飞机游戏
需求分析
本意是做一个类似雷霆战机的小游戏, 但超出了知识范围, 简化成为了一个没有敌人, 生命, 得分, 菜单栏的小游戏, 游戏内容变成了飞机闪躲从固定位置发出的多个子弹, 计算存活时间的样子
游戏窗口通过Frame实现, 双缓冲技术解决闪烁问题
使用paint方法画出所有游戏内容, 且定义了一个线程不断重画窗口
增加键盘监听实现飞机的移动
使用ArrayList<>储存炮弹对象并画出, 并实现炮弹的碰撞反弹
利用返回物体矩形区域判断物体是否相交实现碰撞检测
飞机死亡的爆炸效果通过导入十二张图片然后轮播实现
使用Date打印存活时间
代码实现
Constant
package com.game;
/**
*
* @author 漫磋嗟
* 设置游戏窗口常量,便于统一修改
*/
public class Constant {
static final int CONSTANT_WIDTH = 800;
static final int CONSTANT_HEIGHT = 800;
}
MyFrame
package com.game;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Date;
/**
*
* @author 漫磋嗟
* 游戏主窗口
*/
public class MyFrame extends Frame {
//传入图片作为成员变量
Image bgImg = GameUtil.getImage("images/bc.jpg");
Image planeImg = GameUtil.getImage("images/plane.png");
Plane plane = new Plane(planeImg, 360, 750, 5); //新建飞机对象
ArrayList shellList = new ArrayList(); //新建炮弹数组
Explode explode;//新建爆炸对象
Date startTime = new Date(); //游戏开始时间
Date endTime; //游戏结束时间
@Override
public void paint(Graphics g) {
g.drawImage(bgImg, 0, 0, null); //画背景
plane.drawSelf(g); //画飞机
//画出容器里的所有炮弹
for(int i=0;i
Shell b = shellList.get(i);
b.draw(g);
//飞机和炮弹的碰撞检测
boolean boom = b.getRect().intersects(plane.getRect());
if(boom) {
plane.live = false;
//只进行一次爆炸动画
if(explode == null) {
explode = new Explode(plane.x,plane.y);
}
explode.draw(g);
}
}
//打印游戏结果
if(!plane.live) {
if(endTime == null) {
endTime = new Date();
}
int period = (int)((endTime.getTime()-startTime.getTime())/1000);
printInfo(g, "你存活了"+period+"秒", 50, 230, 300, Color.red);
}
}
//打印信息
public void printInfo(Graphics g, String s, int size, int x, int y, Color color) {
Color c = g.getColor();
g.setColor(color);
Font f = new Font("楷体",Font.BOLD,size);
g.setFont(f);
g.drawString(s, x, y);
g.setColor(c);
}
//增加键盘监听
class KeyMonitor extends KeyAdapter {
@Override
public void keyPressed(KeyEvent e) {
plane.addDirection(e);
}
@Override
public void keyReleased(KeyEvent e) {
plane.minusDirection(e);
}
}
//定义一个不断重画窗口的内部类
class PaintThread extends Thread {
public void run() {
while(true) {
repaint();
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
public void launchFrame() {
setTitle("打飞机");
setSize(Constant.CONSTANT_WIDTH,Constant.CONSTANT_HEIGHT);
setVisible(true);
setLocationRelativeTo(null); //窗口居中
setResizable(false); //窗口不可拉伸
//增加关闭窗口监听
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
//启动重画线程
new PaintThread().start();
//增加键盘的监听
addKeyListener(new KeyMonitor());
//初始化生成炮弹
for(int i=0; i<40; i++) {
Shell b = new Shell();
shellList.add(b);
}
}
public static void main(String[] args) {
MyFrame f = new MyFrame();
f.launchFrame();
}
//双缓冲技术 解决闪烁问题
private Image offScreenImage = null;
public void update(Graphics g) {
if(offScreenImage == null)
offScreenImage = this.createImage(Constant.CONSTANT_WIDTH,Constant.CONSTANT_HEIGHT);
Graphics gOff = offScreenImage.getGraphics();
paint(gOff);
g.drawImage(offScreenImage, 0, 0, null);
}
}
GameUtil
package com.game;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
/**
*
* @author 漫磋嗟
* 用于导入图片的工具类
*/
public class GameUtil {
// 工具类最好将构造器私有化。
private GameUtil() {
}
public static Image getImage(String path) {
BufferedImage bi = null;
try {
URL u = GameUtil.class.getClassLoader().getResource(path);
bi = ImageIO.read(u);
} catch (IOException e) {
e.printStackTrace();
}
return bi;
}
}
GameObject
package com.game;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
/**
*
* @author 漫磋嗟
* 游戏中的物体类
*/
public class GameObject {
Image img;
double x,y;
int width,height;
int speed;
//画自己
public void drawSelf(Graphics g) {
g.drawImage(img, (int)x, (int)y, null);
}
public GameObject() {
}
public GameObject(Image img, double x, double y) {
super();
this.img = img;
this.x = x;
this.y = y;
}
public GameObject(Image img, double x, double y, int width, int height, int speed) {
super();
this.img = img;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speed = speed;
}
//返回物体对应矩形区域 用于碰撞检测
public Rectangle getRect(){
return new Rectangle((int)x,(int) y, width, height);
}
}
Plane
package com.game;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
/**
*
* @author 漫磋嗟
* 游戏中的飞机类
*/
public class Plane extends GameObject {
boolean left,right,down,up;
boolean live = true;
public Plane(Image img, double x, double y, int speed) {
super(img,x,y);
this.speed = speed;
//设置为宽度和高度为图片的高度宽度 便于碰撞检测
this.width = img.getWidth(null);
this.height = img.getHeight(null);
}
public void drawSelf(Graphics g) {
if(live) {
super.drawSelf(g);
if(left) {
x -= speed;
}
if(right) {
x += speed;
}
if(up) {
y -= speed;
}
if(down) {
y += speed;
}
}else {
}
}
//改变飞机的方向
public void addDirection(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
left = true;
break;
case KeyEvent.VK_RIGHT:
right = true;
break;
case KeyEvent.VK_UP:
up = true;
break;
case KeyEvent.VK_DOWN:
down = true;
break;
default:
break;
}
}
public void minusDirection(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
left = false;
break;
case KeyEvent.VK_RIGHT:
right = false;
break;
case KeyEvent.VK_UP:
up = false;
break;
case KeyEvent.VK_DOWN:
down = false;
break;
default:
break;
}
}
}
Shell
package com.game;
import java.awt.Color;
import java.awt.Graphics;
/**
*
* @author 漫磋嗟
* 游戏中的炮弹类
*/
public class Shell extends GameObject {
double degree;
public Shell() {
x = 400;
y = 300;
width = 10;
height = 10;
speed = 5;
//随机生成炮弹角度
degree = Math.random()*Math.PI*2;
}
public void draw(Graphics g) {
//将外部传入对象g的状态保存好
Color c = g.getColor();
g.setColor(Color.yellow);
g.fillOval((int)x, (int)y, width, height);
//炮弹沿着任意角度飞行
x += speed*Math.cos(degree);
y += speed*Math.sin(degree);
//碰到边界炮弹反弹回来
if(y<35 || y>Constant.CONSTANT_HEIGHT-height) {
degree = -degree;
}
if(x<0 || x>Constant.CONSTANT_WIDTH-width) {
degree = Math.PI-degree;
}
//返回给外部,变回以前的颜色
g.setColor(c);
}
}
Explode
/**
*
* @author 漫磋嗟
* 飞机的爆炸效果
*/
public class Explode {
double x,y;
static Image[] imgs = new Image[12];
static {
for(int i=0; i<12; i++) {
imgs[i] = GameUtil.getImage("images/explode/e"+(i+1)+".gif");
imgs[i].getWidth(null);
}
}
int count;
public void draw(Graphics g) {
if(count<12) {
g.drawImage(imgs[count], (int)x, (int)y, null);
count++;
}
}
public Explode(double x, double y) {
this.x = x;
this.y = y;
}
}
总结
使用ArrayList存储炮弹对象
建立一个导入图片的私有类和便于改变窗口长宽的常量类
游戏内物体坐标 (x, y) 一定要设置为double类型, 否则碰撞检测会出错
bug: 飞机没有边界 爆炸效果会出现卡顿或两次
希望能够有 PlaneGame2.0