一、要求:
创建玩家飞机,敌方飞机,均能发子弹,撞击后能移除相应对象。
二、思路:
类的定义以及具体需要实现的功能:
UI//窗体,控制玩家移动的鼠标监听器,刷新窗体的方法,Esc的键盘监听器,游戏的初始化,暂停等
UpdataCenter//刷新窗体的线程
MyPlane//玩家飞机,判断是否被敌人撞,被敌人子弹击中,控制背景移动
Bullet//玩家的子弹,控制自己移动,判断是否和敌方子弹相遇抵消
EnermyPlane//敌方飞机,父类,敌机移动以及出界的相应操作
EnermyPlaneLarge//敌方飞机大号,控制自己移动,判断是否被玩家子弹击中
EnermyPlaneMedium//敌方飞机中号,控制自己移动,判断是否被玩家子弹击中
EnermyPlaneSmall//敌方飞机小号,不发子弹,控制自己移动,判断是否被玩家子弹击中
EnermyThread//创建敌方飞机线程,不定时创建各类敌机的对象
SendShot//随机创建敌方子弹的线程,定时发送子弹攻击玩家
Shot//敌方子弹(自己控制发射)
其它功能:加入分数统计,关卡,生命等数据。
三、具体实现:
UI.java
定义属性:
static ArrayList<Bullet> bulList = new ArrayList<Bullet>();
static ArrayList<Shot> shotList = new ArrayList<Shot>();
static ArrayList<SendShot> ssList = new ArrayList<SendShot>();
static ArrayList<EnermyPlane> enermyList = new ArrayList<EnermyPlane>();
static int score = 0;
static int largeScore = 0;
static int pass = 1;
static int backgroundY = 0;
static boolean pause = false;//当Esc按下的时候暂停所有线程
在窗口上加入JPanel center。重写JPanel的paint()方法,便于重绘。
在paint里面便利队列 绘制所有飞机,子弹,分数等信息。
UpdataCenter.java
重写run()方法,不断center.repaint()。
MyPlane.java
定义属性:
int speed;//飞机移动速度,用键盘控制移动的时候会有用。
int x, y;//飞机当前所在位置,用这个来画,死了之后移到-100,-100。1s后移到xt,yt
int xt, yt;//保存飞机初始时出现的位置
int count = 0, count1 = 0;//用来控制发子弹和背景移动的时间间隔
static int lives = 3;//生命默认3条
int width, height;//飞机图片宽度和高度
boolean canDie = true;//己方每次被撞之后下一条生命有2s无敌时间
draw方法:
public void draw(Graphics g) { g.drawImage(myPlane.getImage(), x, y, width, height, null); }
判断是否被撞的方法以及被撞了之后的处理:
/** * 是否和敌方撞到 */ public void bump() { // 和任何一种飞机相撞 if (canDie) { // 判断是否游戏结束 for (int i = 0; i < UI.enermyList.size() && lives > 0; i++) { EnermyPlane eps = UI.enermyList.get(i); if ((eps.x + eps.width >= x && eps.x <= x + width && eps.y + eps.height / 2 >= y && eps.y <= y + height / 2 - 10) || (eps.x + eps.width / 2 + 20 >= x && eps.x + eps.width / 2 - 20 <= x + width && eps.y + eps.height >= y && eps.y <= y + height)) { UI.enermyList.remove(eps); haveBump(); return; } } // 和敌方子弹相撞 for (int i = 0; i < UI.shotList.size() && lives > 0; i++) { Shot st = UI.shotList.get(i); if (st.x + st.size >= x && st.x <= x + width && st.y + st.size >= y && st.y <= y + height) { UI.shotList.remove(st); haveBump(); } } } } /** * 被撞了 */ public void haveBump() { lives--; explode.play(); if (lives <= 0) { GameOver(); } // 撞到了就移除自己的飞机,过1s再加进来。并且前面2s不会再被撞死 x = -100; y = -100; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } x = xt; y = yt; canDie = false; }
其余方法:
backgroundMove();//控制背景移动
GameOver();//游戏结束的处理
重写run方法:
public void run() {
while (lives > 0) {
try {
if (!UI.pause) {
backgroundMove();
if (!canDie) {
Thread.sleep(2000);
canDie = true;
}//这2s不判断是否被撞
//BUG:这样的话不能发子弹,背景不能移动
bump();
if (count % 5 == 0) {
// 放子弹
Bullet bul = new Bullet(x + width / 2, y);
UI.bulList.add(bul);
new Thread(bul).start();
}
count++;
}
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Bullet.java
定义属性:
int speed;
int x, y;
boolean isLive;
int size = 10;
构造方法中初始化数据:
public Bullet(int x, int y) { speed = 15; this.x = x - size / 2; this.y = y; isLive = true; }
move方法:
void move() { y -= speed; if (y <= 0) { isLive = false; UI.bulList.remove(this); } }
在bump()中判断是否与敌方子弹相撞,重写Thread的run方法,在里面调用move();bump();
while (isLive) {
if (!UI.pause) {
move();
bump();
}
。。。
}
EnermyPlane.java
定义属性:
boolean isLive = true;
Random rd = new Random();
SendShot ss;
int second = rd.nextInt(3000) + 2000;// 发子弹
int x, y,speed;
int width,height;
public void moveDown() {
y += speed;
if (y > centerHeight) {//超过最下面了
isLive = false;
//从队列移除
UI.enermyList.remove(this);
}
}
public void draw(Graphics g){//在子类中实现
}
EnermyPlaneSmall.java(其余两类飞机一样的)
构造方法中初始化数据:
public EnermyPlaneSmall() {
speed = 2 + UI.pass;
isLive = true;
width = enermyPlaneSmall.getIconWidth() / 2;
height = enermyPlaneSmall.getIconHeight() / 2;
x = rd.nextInt(400);
y = -height;
//ss = new SendShot(this);//发子弹
//UI.ssList.add(ss);
//new Thread(ss).start();
}
在bump中判断是否被玩家子弹击中。
EnermyThread.java
run()中的关键代码:
while (MyPlane.lives > 0) {// 我方还存在生命 // 每隔5s出3-6个小的,共出4-6次. if (!UI.pause) { for (int k = 0; k < 2; k++) { for (int j = 0; j < rd.nextInt(4) + 2 && MyPlane.lives > 0; j++) { for (int i = 0; i < rd.nextInt(3) + 3; i++) { EnermyPlaneSmall eps = new EnermyPlaneSmall(); UI.enermyList.add(eps); new Thread(eps).start(); } Thread.sleep(4000); } // 出2个中的 for (int i = 0; i < 2 && MyPlane.lives > 0; i++) { EnermyPlaneMedium epm = new EnermyPlaneMedium(); UI.enermyList.add(epm); new Thread(epm).start(); } Thread.sleep(3000); } // 出来一个Boss if (MyPlane.lives > 0) { EnermyPlaneLarge epl = new EnermyPlaneLarge(); UI.enermyList.add(epl); new Thread(epl).start(); } } }
SendShot.java
在run中生成Shot对象,并Thread.sleep(ep.second);
如果这个飞机被打死了,这个对象就移出队列,且不再生成Shot对象了。
Shot.java
定义属性:
int size = 4;
EnermyPlane ep;
int speed = 8;
int x, y;
boolean isLive = true;
@Override public void run() { while (isLive) { if (!UI.pause) { isLive = ep.isLive; moveDown(); } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } if (!ep.isLive) {// 清除剩余的子弹 UI.shotList.remove(this); } } void draw(Graphics g) { g.setColor(Color.white); g.fillOval(ep.x + ep.width / 2, y, size, size); } public void moveDown() { y += speed; if (y >= 600) { UI.shotList.remove(this); } }
附上效果图:
当前已知bug:
撞击判断有很大的误差,最高分有时候会比分数小,游戏结束了后还不能重新开始,玩家挂了一次之后2s无敌时间导致不能发子弹而且背景也不动。