Java第十一课——多线程实现飞机大战

一、补充讲解一下线程

在第九课的基础上补充两点:
1、启动线程使用start()方法而不直接使用rin()方法,因为线程是一直重复执行的,调用run方法只会执行一次,所以用start方法启动线程
2、当小球跑的很远,跑出窗体时,便可以把小球移出去,用remove()方法

list.remove(ball)

可以给小球加入一个getX()方法获取小球的x坐标,当x大于窗体长度时移除。因为小球是放在队列里面的,所以队列里可能有很多小球,那么可以用遍历的方式获取到要移除的小球在队列里的位置。值得一提的是:先放进去的小球在队列的前面,就像排队一样

for (int i = listmybullet.size() - 1; i > 0; i--){Ball b = list.get(i);if (b.getX() > frame.getWidth()){list.remove(i);}
}

二、飞机大战的实现思路

在完成飞机大战之前先明确要什么组件?或者说飞机大战有什么对象:需要有一个背景,自己的飞机,敌机,子弹,道具,Boss等等。还可能需要哪些方法?移动的方法,绘制图片的方法,获取坐标的方法(用于判断碰撞),碰撞了怎么处理的方法…
不难发现,几乎所有的对象都要用到以上的方法,于是可以写一个接口,包含以上所有方法,让所有组件的类连到这个接口就可以了
然后就确定哪些可以利用线程来更方便的实现。举个例子,我机只有一架,那实现的时候就只要创建一个对象,写他的移动方法和绘制方法就好了。但我机的子弹,假设是每过1秒自动发射1颗子弹,那么就可以利用第九节课讲的:利用队列实现,每过一秒给队列放进一个小球,最后启动。
另外,之前讲过的Timer和TimerTask可以用来实现一些比较简单的线程。举个例子:敌军飞机出现的数量会受到Boss的影响,Boss出现后肯定要减少敌机出现的数量。而道具,道具的出现与场上的情况没有什么联系,而且道具也是可以重复出现的。那么道具就可以用Timer来实现

三、飞机大战的具体实现

1、先创建一个接口:Entity,并在里面写所需要的方法

public interface Entity {public void move();public void draw(Graphics g);public int getX();public void getY();public void hit(int i);
}

这里的hit方法里的形参 i 是用于移除列队的。当子弹撞击后,获取到这个子弹在队列里的位置,然后用remove方法,敌机也是同理。
2、窗体
移动飞机的方式一般用键盘,所以我在这里我就和监听器就放在一起写了

public class Frame implements KeyListener {public void UI(){JFrame frame=new JFrame();frame.setSize(500, 1000);frame.setVisible(true);Graphics g=frame.getGraphics();frame.addKeyListener(this);}public static void main(String[] args) {new Frame().UI();}public void keyPressed(KeyEvent e) {int code = e.getKeyCode();//获取到按下的按钮后传给我机的move()}public void keyTyped(KeyEvent e) {}public void keyReleased(KeyEvent e) {}
}

这个窗体只是一个小框架,还缺了很多东西(启动线程),等创建好每个类后再回来修改
在窗体上有个很重要的东西:缓存。有了缓存画布之后就不会卡顿了,不然对象太多会非常卡
在UI()方法里:

public void UI(){...frame.addKeyListener(this);//创建缓存图片对象BufferedImage bufferImage = new BufferedImage(frame.getWidth(), 2 * frame.getHeight(), BufferedImage.TYPE_INT_RGB);//从缓存图片获取画布对象Graphics bufferedG=bufferImage.getGraphics();//在所有元素没有画完前,不显示//这里要把所有对象都找出来,调用他们的draw方法,同时move方法也可以在这里一起调用//把缓存图片显示出来g.drawImage(bufferImage, 0, -1000, null);try {Thread.sleep(20);} catch (InterruptedException e) {}
}

这段代码也缺失了一部分(找出所有对象调用draw(Graphics g))在最后会补全
另外,可以看到,缓存图片的高度是窗体的两倍高,原因是可以使背景和敌机从最顶上出现,也就是使敌机和背景可以无缝的进入窗体中而不是突然出现,同时他们出现的位置也得是负的,用move方法使他们"滑"进来,所以显示图片时从-1000开始画
3、背景
背景就需要一张图片,这里有飞机大战的素材库:
http://www.aigei.com/view/71627.html?order=down
在里面找一张背景图,可以根据背景来修改窗体参数,把图片复制进环境,可以用Image来获取复制进来的图片
新建一个背景类,并实现Entity接口。接口的move和draw方法可以帮助我们实现背景的动态效果。
draw方法里,只要当图片的坐标大于图片的高度时,把坐标清零就可以了,换句话说就是当图片画出窗体时重新在原点绘制一张。
move方法可以定义背景的移动速度,也可以根据场景来定义速度。另外因为飞机是从下往上飞的,但实际上,我们不控制飞机时,飞机在窗体上是不动的,动的是背景,所以背景是向下动的,那就要使 y+ > 0。

public class Background implements Entity {Image image = new ImageIcon("planebg.png").getImage();int x,y;public void move() {y += 2;}public void draw(Graphics g) {if (y - image.getHeight(null) >= 0) {y = 0;}g.drawImage(image, x, y, null);g.drawImage(image, x, y + image.getHeight(null), null);}public int getX() {return x;}public int getY() {return y;}public void hit(int i) {}
}

背景的坐标是相对缓存画布而定的,创建全局变量之后被初始化为0,这个0相对Frame是-1000,所以两个drawImage方法相互配合,初始化时一张图片在窗体上,另一张图片在窗体外,两张图片一起向下动,下面的图片画完了就会跑到上面重现画,上面的图片向下填补
背景类写完之后,就可以在Frame的把背景画进缓存

public class Frame implements KeyListener {Background bg=new Background();public void UI(){...while(true){...//在所有元素没有画完前,不显示bg.draw(bufferedG);bg.move();...}}
}

要注意的是:背景的对象不能在缓存线程(while(true))里面创建,因为背景只有一个,不能在线程里一直创建
4、我机
和背景一样实现Entity接口。
在素材库里找一张我机的图片,但这里和背景不同的是,我机是要控制大小的,不可能我机和背景一样大,可以通过这个方法来控制大小:
drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
如:把大小控制在90×90

g.drawImage(image, x, y, 90, 90, null);

我机也是要控制出现位置的:比如初始化时想让我机出现在正中间,就要算好我机的尺寸来调整
draw方法只要把图片绘制上去就可以了。当然会有个问题,飞机可以飞出窗体,那就可以写if语句来控制(下面的代码没有限制飞机能否飞出去)
move方法就要结合监听器来写,在Frame方法里获取到了code来控制我机移动,但要注意的是,move方法里不能带形参(Entity接口里move方法没有形参),所以code不能通过move方法的形参来传递。那可以另外写一个带形参的move方法,或是直接传值(下面的方法是直接赋值的)

public class Myplane implements Entity{int x=250;int y=1850;int speed=10;int code;//用于KeyListener的检测Image image=new ImageIcon("myplane.png").getImage();public void move() {if(code==KeyEvent.VK_UP){y -=speed;}if(code==KeyEvent.VK_DOWN){y+=speed;}if(code==KeyEvent.VK_LEFT){x-=speed;}if(code==KeyEvent.VK_RIGHT){x+=speed;}}public void draw(Graphics g) {g.drawImage(image, x-44, y, null);}public int getX() {return x;}public int getY() {return y;}public void hit(int i){}
}

这里要注意初始y坐标是大于窗体的高度的,因为这里的坐标是相对缓存画布而定的,而缓存画布是两倍的窗体高度。另外x,y坐标和g.drawImage(image, x-44, y, null)里面的值都是根据自己的飞机大小定的。hit方法在后面来补充
在窗体里:

public class Frame implements KeyListener {Background bg=new Background();Myplane mp=new Myplane();public void UI(){...while(true){...//在所有元素没有画完前,不显示bg.draw(bufferedG);bg.move();mp.draw(bufferedG);...}}...public void keyPressed(KeyEvent e) {int code = e.getKeyCode();//获取到按下的按钮后传给我机的move()mp.code=code;mp.move();}...
}

5、敌机
实现Entity接口
敌机的变化可以比较多,出现的位置,移动的方向,移动的速度,能否发射子弹等等,所以不太适合用Timer和TimerTask来用,下面的代码里我只写了一些简单的敌机条件
draw方法直接画出即可
move方法加了敌机不同的移动速度,用Random来实现随机。
另外敌机的位置坐标用构造方法来随机生成

public class Enemyplane implements Entity{int x;int y=600;Random rand=new Random();int a=rand.nextInt(2);//敌机速度判定 Image image=new ImageIcon("enemyplane.png").getImage();public Enemyplane() {Random randy=new Random();this.x=randy.nextInt(340)+80;}public void move() { if(a==0){y+=2;}else if(a==1){y+=4;}}public void draw(Graphics g) {g.drawImage(image, x, y, 90, 90, null); }public int getX() {return x;}public int getY() {return y;}
}

敌机和背景、我机不同的是,敌机有很多架,那需要一个队列来实现出现敌机的多线程
先在Frame里创建队列:

ArrayList<Entity> listenemy =new ArrayList<Entity>();

另外写一个类用来控制小球的移动并继承Thread重写run方法

public class EnemyplaneMove extends Thread {private ArrayList<Entity> listenemy;public EnemyplaneMove (ArrayList<Entity> listenemy) {this.listenemy=listenemy;}public void run(){while(true){Enemyplane ep=new Enemyplane();listenemy.add(ep);}try{Thread.sleep(1000);//敌机出现的间隔}catch(Exception e){}}
}

然后在窗体里启动:

public class Frame implements KeyListener {ArrayList<Entity> listenemy =new ArrayList<Entity>();Myplane mp=new Myplane();Background bg=new Background();public void UI(){...Graphics g=frame.getGraphics();EnemyplaneMove em=new EnemyplaneMove(listenemy);em.start();while(true){...//在所有元素没有画完前,不显示bg.draw(bufferedG);bg.move();//找到所有的敌机for(int i=0;i<listenemy.size();i++){Entity entity=listenemy.get(i);   entity.draw(bufferedG);entity.move();}mp.draw(bufferedG);...}}
}

6、子弹
子弹可以有一些变化:比如不同颜色子弹的威力不同,飞行方向不同,飞行速度不同等等。而子弹在不同的对象上的表现也不同,比如我机的子弹可以有较多的变化:多方向,连发,而敌机的子弹变化肯定会比我机少,而Boss的子弹也会有很多变化。所以子弹类可以只有一个,用来专门处理子弹的不同,子弹的移动就需要根据不同对象来写多个不同的移动方法。
子弹类:实现Entity接口
draw方法和move方法都很简单,毕竟子弹类只管子弹本身的特性,不管是谁发出的。换句话说,子弹的飞行轨迹,威力,大小,颜色都是子弹自己的性质,从哪里飞出去,什么时候可以发射子弹,发射多少颗子弹,向哪个方向发射子弹就是子弹移动类管的事了

public class Bullet implements Entity{int m;//0表示子弹向上飞,1表示子弹向左上飞,2表示子弹向右上飞int x;int y;Image image=new ImageIcon("bullet1.png").getImage();public void move() {if(m==0){y-=9;}else if(m==1){y-=7;x-=2}else if(m==2){y-=7;x+=2;}}public void draw(Graphics g) {g.drawImage(image, x, y, null);  }public int getX() {  return x;}public int getY() {  return y;}public void hit(int i){}
}

然后在Frame类创建子弹的队列

ArrayList<Entity> listmybullet =new ArrayList<Entity>();

创建控制我机子弹移动的类,继承Thread。
我机的子弹是从我机身上发出的,那么就要获取到我机的坐标,再根据子弹的大小调整子弹发射时的位置。可以利用构造方法来获取我机的对象

public class MyBulletMove extends Thread{private Myplane mp;private ArrayList<Entity> listmybullet;public MyBulletMove (ArrayList<Entity> listmybullet, Myplane mp){this.listmybullet=listmybullet;this.mp=mp;}public void run(){while(true){Bullet upbullet=new Bullet();upbullet.m=0;//向上飞upbullet.x=mp.x-11-mp.a;upbullet.y=mp.y;listmybullet.add(upbullet);try{Thread.sleep(400);//子弹发射的间隔}catch(Exception e){}}}
}

然后就是在窗体里启动,并在缓存里获取所有对象

public class Frame implements KeyListener {  ArrayList<Entity> listenemy =new ArrayList<Entity>();ArrayList<Entity> listmybullet =new ArrayList<Entity>();Myplane mp=new Myplane();Background bg=new Background();public void UI(){...Graphics g=frame.getGraphics();EnemyplaneMove em=new EnemyplaneMove(listenemy);em.start();MyBulletMove bm=new MyBulletMove(listmybullet, mp);bm.start();while(true){...//在所有元素没有画完前,不显示bg.draw(bufferedG);bg.move();//找到所有的敌机for(int i=0;i<listenemy.size();i++){Entity entity=listenemy.get(i);entity.draw(bufferedG);entity.move();}//找到所有我机子弹for(int i=0;i<listmybullet.size();i++){Entity entity=listmybullet.get(i);entity.draw(bufferedG);entity.move();}mp.draw(bufferedG);...}}
}

7、碰撞判断
碰撞判断也是一个线程,每过多少秒就检测碰撞,这个类需要的对象有我机,所有子弹,所有敌机,通过构造方法获取我机,两个队列。

public class Checkforcrash extends Thread{private ArrayList<Entity> listmybullet;private ArrayList<Entity> listenemy;private Myplane mp;public Checkforcrash(ArrayList<Entity> listenemy,ArrayList<Entity> listmybullet, Myplane mp) {this.listenemy=listenemy;this.listmybullet=listmybullet;this.mp=mp;}public void run() {while (true) {for (int i = listmybullet.size() - 1; i > 0; i--) {Entity temA = listmybullet.get(i);for (int j = 0; j < listenemy.size(); j++) {Entity temB = listenemy.get(j);if (Tools.enemymycrash(temB, mp)) {temA.hit(j);mp.hit(j);}if (Tools.bulletenemycrash(temA, temB)) {temA.hit(i);temB.hit(j);}}}}}}

这里有另外一个类Tools来辅助判断,因为判断条件包括x和y的坐标差的绝对值,比较长,直接放进去可读性比较低,另外,Tools类可以与当前类配合实现道具的拾取。
在判断绝对值时,先要根据对象的图片大小来确定图片中心点,然后再确定二者相差多少时为撞上了

public class Tools {// 敌机我机碰撞public static boolean enemymycrash(Entity e, Myplane mp) {if (Math.abs((e.getX() + 45) - (mp.getX() - 1)) < 40 && Math.abs((e.getY() + 45) - (mp.getY() + 55)) < 40) {return true;} else {return false;}}// 敌机子弹相撞public static boolean bulletenemycrash(Entity ea, Entity eb) {if ((ea instanceof Enemyplane) && (eb instanceof Bullet)) {if (Math.abs((ea.getX() + 45) - (eb.getX() + 10)) < 20 && Math.abs((ea.getY() + 45) - eb.getY()) < 20) {return true;}} else if ((ea instanceof Bullet) && (eb instanceof Enemyplane)) {if (Math.abs((ea.getX() + 10) - (eb.getX() + 45)) < 20 && Math.abs(ea.getY() - (eb.getY() + 45)) < 20) {return true;}}return false;}
}

A instanceof B 用于判断A是否是B类的对象
最后便是实现各自的hit方法:
(1)子弹:

public class Bullet implements Entity{...ArrayList<Entity> list;...public void hit(int i){//移除队列list.remove(i);}
}

(2)子弹移动:

public class MyBulletMove extends Thread{...public void run(){while(true){Bullet upbullet=new Bullet();...   upbullet.list=listmybullet;listmybullet.add(upbullet);...}}
}

upbullet.list=listmybullet;也可以用构造方法
(3)敌机

public class Enemyplane implements Entity{...ArrayList<Entity> list;public void hit(int i) {list.remove(i);}
}

(4)敌机移动

public class EnemyplaneMove extends Thread {...public void run(){while(true){Enemyplane ep=new Enemyplane();listenemy.add(ep);ep.list=listenemy;...}}
}

(5)我机
可以找一张飞机残骸的图片,把image换掉

public class Myplane implements Entity{...public void hit(int i) {image = new ImageIcon("brokenplane.png").getImage();}
}

到这里飞机大战的雏形就结束了!道具的Boss同上类似,可以自己添加
PS:文章篇幅较长,可能会有一些错误,望指正(抱拳)

Java第十一课——多线程实现飞机大战相关推荐

  1. 基于Java语言在窗体上实现飞机大战小游戏

    全套资料下载地址:https://download.csdn.net/download/sheziqiong/85594271 项目介绍 飞机大战:用 Java 语言在窗体上实现飞机大战小游戏,运行程 ...

  2. Java Swing 经典小游戏《飞机大战》———— (四)碰撞检测 游戏状态与得分 玩家升级

    前期回顾 Java Swing 经典小游戏<飞机大战>---- (一)获取素材,创建窗口,添加滚动背景,双缓冲 Java Swing 经典小游戏<飞机大战>---- (二)玩家 ...

  3. Java Swing 经典小游戏《飞机大战》———— (一)获取素材,创建窗口,添加滚动背景,双缓冲

    (一)最终效果 1.窗口 2.项目框架 (二)效果实现 1.获取素材 获取步骤省略,大家可自行到我的资源获取,放置在src目录下 2. 创建窗口 WinGame.java import java.aw ...

  4. JAVA day10、11、12 飞机大战

    1.小敌机(c) /*** 小敌机: 是飞行物,也是敌人**/ public class Airplane extends FlyingObject implements Enemy {int spe ...

  5. Java多线程编写简易飞机大战(一)

    ** Java多线程编写简易飞机大战(一) ** 利用多线程编写飞机大战,主要有3个关键: ①继承Thread类,重写run方法: ②线程工作代码在run方法中写: ③启动时,调用线程对象的start ...

  6. java飞机大战爆炸效果_Java飞机大战游戏设计与实现

    1 概述 1.1 项目简介 本次Java课程设计是做一个飞机大战的游戏,应用Swing编程,完成一个界面简洁流畅.游戏方式简单,玩起来易于上手的桌面游戏.该飞机大战项目运用的主要技术即是Swing编程 ...

  7. java飞机大战编程_[源码和文档分享]Java飞机大战游戏设计与实现

    1 概述 本次Java课程设计是做一个飞机大战的游戏,应用Swing编程,完成一个界面简洁流畅.游戏方式简单,玩起来易于上手的桌面游戏.该飞机大战项目运用的主要技术即是Swing编程中的一些窗口类库. ...

  8. 怎么用java做全民飞机大战_Java飞机大战游戏设计与实现

    1 概述 本次Java课程设计是做一个飞机大战的游戏,应用Swing编程,完成一个界面简洁流畅.游戏方式简单,玩起来易于上手的桌面游戏.该飞机大战项目运用的主要技术即是Swing编程中的一些窗口类库. ...

  9. 飞机大战java代码_[源码和文档分享]Java飞机大战游戏设计与实现

    1 概述 1.1 项目简介 本次Java课程设计是做一个飞机大战的游戏,应用Swing编程,完成一个界面简洁流畅.游戏方式简单,玩起来易于上手的桌面游戏.该飞机大战项目运用的主要技术即是Swing编程 ...

最新文章

  1. Control usage: (1) Windows Phone 7: Popup control
  2. 千里之堤毁于蚁穴------重点项目不能交付之谜(一)泥淖中的验收测试
  3. 互联网协议 — RTSP 实时流传输协议
  4. jquery 源码分析九 - Sizzle
  5. case when用法java_Oracle CASE WHEN 用法介绍
  6. python3的3D实战-基于panda3d(2)
  7. Print out Android kernel log
  8. 自媒体各大平台收益对比_哪些自媒体平台没有新手期,适合小白撸收益?
  9. 他高考数学仅得15分,清华校长复查后激动拍板:这名学生,我要了
  10. Bootstrap 分页导航
  11. 销售数据分析这么做,领导不重用你都难
  12. LYNC2013部署系列PART7:TMG部署
  13. 前端学习之--CSS
  14. js实现浏览器打印PDF
  15. 杰奇1.7--关关采集器使用教程
  16. 小米笔记本pro 双硬盘双系统 opencore引导安装黑苹果
  17. 如何解决Python中的RuntimeWarning: invalid value encountered in double_scalars问题
  18. 成品系统搭建 一周就可以上线运营
  19. Birt报表开发工具及Birt runtime部署
  20. 计算机无法安装windows系统怎么办,电脑无法安装Win10怎么解决

热门文章

  1. aspen二元体系共沸组分_共沸剂二元体系汽液平衡数据的测定及关联.pdf
  2. 雅可比迭代和高斯—赛德尔迭代法
  3. OAuth 统一登录 记录
  4. libreoffice使用_使用LibreOffice作为您的开源预算工具
  5. SDL2 简明教程(二):创建一个空的窗口
  6. ShopsN已经进入跨境电商提供商领域并取得了飞速发展
  7. 【FPGA学习】ISE调试助手:逻辑分析仪(ChipScope Pro)
  8. NFS- CentOS7安装使用NFS服务器
  9. R语言 Scale函数
  10. Nonbsp;beannbsp;namednbsp;#039;/mlogin#039;nbsp;isnbsp;defin…