一、结构框架与思路
![](https://img-blog.csdnimg.cn/img_convert/0588aab88bc086be31bdc37666d87f7f.png)
运用多线程实现的飞机大战:
①ThreadGame 主线程 实现1、绘制 角色 背景 2、移动 3、检测碰撞
②AutoThreadFly 实现1、间隔生成敌机对象,并将敌机对象存入一个List中
③AutoBullet 实现1、遍历敌机的List 取出每一个敌机对象,利用其坐标生成一颗子弹,加入子弹List中
④监听器线程 实现1、修改玩家的坐标 2、生成玩家子弹加入玩家子弹List中
二、图片展示
![](https://img-blog.csdnimg.cn/img_convert/1dd33d0d43bebf30d8a6b67267a4079d.png)
三、代码实现
1、游戏界面
import javax.swing.*;
import java.util.ArrayList;
public class GameUI extends JFrame {
//监听实例
GameListener gl = new GameListener();
//创建飞机队列和子弹队列
ArrayList<FlyObject> flyList = new ArrayList<>();
ArrayList<FlyObject> bulletList = new ArrayList<>();
ArrayList<FlyObject> EnemyBulletList = new ArrayList<>();
//玩家对象
PlayFigther player = PlayFigther.getMyFigther();//调用玩家对象
/**
* 游戏界面
*/
public void initUI(){
setTitle("清风保卫战");//设置游戏名字
setDefaultCloseOperation(EXIT_ON_CLOSE);//关闭窗体即关闭程序
setSize(GameData.FrameWidth,GameData.FrameHeight);//设置窗体大小
setVisible(true);//设置可视化
System.out.println("Interface loading completed!");
addMouseMotionListener(gl);//实现鼠标监听
addKeyListener(gl);//实现键盘监听
gl.setPlayer(player);//
gl.setBulletList(bulletList);
/****** 启动线程 ******/
//游戏主线程
GameThread gt = new GameThread(flyList,bulletList,EnemyBulletList,getGraphics(),player);
Thread t = new Thread(gt);
t.start();
//敌机线程
new Thread(new AutoThread(flyList)).start();
//敌机子弹线程
new Thread(new AutoBullet(flyList,EnemyBulletList)).start();
}
public static void main(String[] args) {
new GameUI().initUI();//调用方法启动游戏界面
}
}
我们在一开始就把 飞机队列和子弹队列(自己飞机和敌机子弹)初始化,并且还创建监听对象,后续控制飞机做准备(鼠标和键盘控制),再调用玩家对象。在窗体内启动三个线程:①游戏主线程②敌机线程③敌机子弹线程,,这三个线程的具体功能在前面都有述说。
2、接下来我们先设置飞机类,想想你的飞机有啥属性,有啥方法,首先你会想到你飞机得动吧,动的时候还得有速度吧,你的飞机有多大呢,你的飞机是怎么一个飞机(战斗机、直升机),最后可能后续才考虑到:该飞机是否还存活,换句话来说就是说这架飞机被子弹击落了没。
我们就得到飞机属性:x-y-speedx-speedy-width-height-color-isLive
然后我们来想想你的飞机有哪些方法,会发射炮弹是一下就想到的吧,绘制 移动 开火 检测碰撞。。。好了我给你想好了
package FlyGame_v3;
import java.awt.*;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.applet.Applet;
import java.applet.AudioClip;
import java.io.File;
import java.net.URI;
import java.net.URL;
/**
* 封装飞机类
*/
public class FlyObject {
//x,y代表飞机的坐标位置
private int x,y;
//width,height代表飞机的长宽即大小
private int width,height;
//speedx,speedy代表飞机移动的速度
private double speedx,speedy;
//飞机的背景颜色
private Color color;
private boolean islive = true;
//构造方法
public FlyObject(int x, int y, int width, int height, double speedx, double speedy, Color color) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speedx = speedx;
this.speedy = speedy;
this.color = color;
}
//功能方法 : 绘制 移动 开火 检测碰撞
public void draw(Graphics g){
if(islive){
g.setColor(color);
}}
public void move() {
if (islive) {
x += speedx;
y += speedy;
}
}
public void fire(ArrayList<FlyObject> flyObjects){//开火接口
}
//封装的set get方法
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public double getSpeedx() {
return speedx;
}
public void setSpeedx(double speedx) {
this.speedx = speedx;
}
public double getSpeedy() {
return speedy;
}
public void setSpeedy(double speedy) {
this.speedy = speedy;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public boolean isIslive() {
return islive;
}
public void setIslive(boolean islive) {
this.islive = islive;
}
public void playmus(){// 背景音乐播放
File file = new File("D:\\java\\86153\\DoProject\\backgroundmusic\\雷火-爆炸.wav");
AudioClip ac = null;
try{
ac = Applet.newAudioClip(file.toURL());
}catch (MalformedURLException e){
throw new RuntimeException(e);
}
ac.play();
}
/**
* 传入一个飞行对象 与 调用方法者 比较
* @param flyobj
* @return
*/
public boolean isCollsion(FlyObject flyobj) {//检测碰撞
if(flyobj==null){
return false;
}
if (islive&& flyobj.isIslive()) {
if (flyobj != null) {
if (this.x + this.width > flyobj.x && this.x < flyobj.x + flyobj.width && this.y + this.height > flyobj.y && this.y < flyobj.y + flyobj.height) {
//System.out.println("");
playmus();
return true;
}
}
}
return false;
}
}
该飞机对象很多属性其实跟导弹属性一样,所以后续我们实现导弹时直接继承飞机类就行了。
这儿有几个难点:
①检测碰撞——敌机子弹与我机、我方子弹与敌机、敌机与我机
②背景音乐——飞机爆炸后的音效、背景音乐、子弹发射声效
③飞机isLive是否存活属性(初始化为ture),在后续判读很多程序是否执行
④飞机move移动属性是通过在一个单位时间内移动n个像素
⑤绘制功能实现
3、玩家飞机对象
package FlyGame_v3;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class PlayFigther extends FlyObject {
int score = 100;//初始分数(生命值)
ImageIcon iic = new ImageIcon("images/myfly.png");//我的飞机模型
Image image = iic.getImage();
public PlayFigther(int x, int y, int width, int height, double speedx, double speedy, Color color) {
super(x, y, width, height, speedx, speedy, color);
}
public static PlayFigther getMyFigther(){
return new PlayFigther(400,400,80,80,0,0,Color.red);
}
@Override
public void draw(Graphics g) {
super.draw(g);
// g.fillRect(getX(),getY(),getWidth(),getHeight());
if(isIslive())
g.drawImage(image,getX(),getY(),80,80,null);
}
@Override
public void move() {
//我方战机的移动需要使用键盘来控制
if(isIslive())
super.move();
}
public void fire(ArrayList<FlyObject> bulletList){
//依靠玩家的坐标生成 子弹对象加入一个队列中
if(isIslive()){
MyBullet bullet = new MyBullet(getX()+40,getY(),10,10,0,25,Color.YELLOW);
bulletList.add(bullet);
}}
public void up(){
setSpeedy(-10);
}
public void down(){
setSpeedy(10);
}
public void left(){
setSpeedx(-10);
}
public void right(){
setSpeedx(10);
}
}
在玩家飞机类里,我们可以自由设置自己飞机,比如打这个关卡我用某某战斗机,还有像很多游戏里都有这么一个东西--我的仓库里面都是一些自己装备。
在这个玩家飞机里我们设置了
public void fire(ArrayList<FlyObject> bulletList){
//依靠玩家的坐标生成 子弹对象加入一个队列中
if(isIslive()){
MyBullet bullet = new MyBullet(getX()+40,getY(),10,10,0,25,Color.YELLOW);
bulletList.add(bullet);
}
}
说明当我们使用开火键(空格)需要传到这一个队列,这个队列也就是在GameUI类的开始初始化的那个队列。当把队列传过来的时候,我们这时就获取该飞机的位置,从而得到飞机发出子弹位置,并将其保存下来存入队列,后续画出来(再设置该子弹的速度移动),子弹就形成。
这里我们得注意,这里getX(),getY()的方法只是获取该飞机的左上角的那个位置(把飞机看成一个矩形,就是左上角那个顶点),所以我们得调整发出子弹的位置(比如x+width/2).
4、安装监听器
package FlyGame_v3;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
public class GameListener implements MouseMotionListener, KeyListener {
PlayFigther player;
ArrayList<FlyObject> bulletList;
public void setPlayer(PlayFigther player){
this.player = player;
}
public void setBulletList(ArrayList<FlyObject> bulletList){
this.bulletList = bulletList;
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == KeyEvent.VK_SPACE){
player.fire(bulletList);
}
if(keyCode == KeyEvent.VK_W){
player.up();
}
if(keyCode == KeyEvent.VK_S){
player.down();
}
if(keyCode == KeyEvent.VK_A){
player.left();
}
if(keyCode == KeyEvent.VK_D){
player.right();
}
}
@Override
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if(keyCode == KeyEvent.VK_W || keyCode == KeyEvent.VK_S ){
player.setSpeedy(0);
}
if(keyCode == KeyEvent.VK_A || keyCode == KeyEvent.VK_D ){
player.setSpeedx(0);
}
}
@Override
public void mouseDragged(MouseEvent e) {
}
@Override
public void mouseMoved(MouseEvent e) {
}
}
在监听器里我们设置了通过监听键盘实现移动,按住键盘w就会调用up方法,也就是向上移动。
我们还要注意需要将子弹队列(自己飞机)传过来,因为在按住开火指令时,需要调用飞机类的fire方法,该方法参数为队列,然后将子弹存入队列,在绘制线程里再画出来。
5、玩家飞机子弹类、敌机类、敌机子弹类
package FlyGame_v3;
import java.awt.*;
public class MyBullet extends FlyObject {//继承飞行物
public MyBullet(int x, int y, int width, int height, double speedx, double speedy, Color color) {
super(x, y, width, height, speedx, speedy, color);
}
@Override
public void draw(Graphics g) {//重写绘制方法
super.draw(g);
if(isIslive())
g.fillOval(getX(),getY(),getWidth(),getHeight());
}
@Override
public void move() {//重写移动方法
if(isIslive())
setY((int) (getY()-getSpeedy()));
}
}
package FlyGame_v3;
import java.awt.*;
public class EnemyBullet extends MyBullet {
public EnemyBullet(int x, int y, int width, int height, double speedx, double speedy, Color color) {
super(x, y, width, height, speedx, speedy, color);
}
@Override
public void draw(Graphics g) {
super.draw(g);
if(isIslive())
g.fillOval(getX(),getY(),getWidth(),getHeight());
}
@Override
public void move() {
if(isIslive())
setY((int) (getY()+getSpeedy()));
}
}
package FlyGame_v3;
import javax.swing.*;
import java.awt.*;
public class EnemyFigther extends FlyObject {
static Image image = new ImageIcon("images/ef.png").getImage();
public EnemyFigther(int x, int y, int width, int height, double speedx, double speedy, Color color,boolean islive) {
super(x, y, width, height, speedx, speedy, color);
}
@Override
public void draw(Graphics g) {
super.draw(g);
if(isIslive())
g.drawImage(image,getX(),getY(),getWidth(),getHeight(),null);
}
@Override
public void move() {
if(!isIslive()){
return;
}
if(getX()<0 || getX()> GameData.FrameWidth){
setSpeedx(-getSpeedx());
}
setY((int) (getY()+getSpeedy()));
setX((int) (getX()+getSpeedx()));
}
}
这三个类都差不多,都是重写draw、move方法。
6、AutoThread线程:理解为主要是去计算 随机出现的敌机,发出子弹的速度(需要遍历敌机队列获取敌机位置)计算好后,都存入相应队列中,后续在主线程里遍历绘制出来。
package FlyGame_v3;
import java.awt.*;
import java.util.ArrayList;
import java.util.Random;
public class AutoThread implements Runnable {
ArrayList<FlyObject> FlyList;
public AutoThread(ArrayList<FlyObject> flyList) {
FlyList = flyList;
}
Random ran = new Random();
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//准备敌机数据
int x = ran.nextInt(GameData.FrameWidth);
int y = ran.nextInt(100)-100;
double speedx = ran.nextInt(3)+1;
double speedy = ran.nextInt(5)+2;
int w = 80;
int h = 110;
Color color = Color.black;
EnemyFigther ef = new EnemyFigther(x,y,w,h,speedx,speedy,color,true);
FlyList.add(ef);
}
}
}
class AutoBullet implements Runnable{
ArrayList<FlyObject> flyList;
ArrayList<FlyObject> EnemyBulletList;
public AutoBullet(ArrayList<FlyObject> flyList, ArrayList<FlyObject> EnemyBulletList) {
this.flyList = flyList;
this.EnemyBulletList = EnemyBulletList;
}
Random ran = new Random();
@Override
public void run() {
while(true){
try {
Thread.sleep(ran.nextInt(50)+800);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//检查队列是否有数据
//System.out.println(flyList.toString());
for (int i = 0; i < flyList.size(); i++) {
FlyObject fly = flyList.get(i);
if(fly.isIslive()){
int x = fly.getX();
int y = fly.getY();
EnemyBullet eb = new EnemyBullet(x+40, y+110, 10, 15, 0, 10, Color.red);
EnemyBulletList.add(eb);
//System.out.println("add");
}}
}
}
}
注意:在run方法里,记得设置死循环,让它一直传入一个数据就计算。
飞机移动的,发出移动的子弹其原理就是先前设置好飞机运动规则,然后计算出其属性,再将这些计算出来的属性存入队列中,在线程每大约30ms(自己可以设置)(相当于帧)遍历一次,也就是把那个时间点的图片画出来。
7、GameThread 主线程
package FlyGame_v3;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
public class GameThread implements Runnable{
ArrayList<FlyObject> flyList;
ArrayList<FlyObject> bulletList;
ArrayList<FlyObject> EnemyBulletList;
Graphics g;
PlayFigther player;
//背景图
static Image image = new ImageIcon("images/bj.jpg").getImage();
public GameThread(ArrayList<FlyObject> flyList, ArrayList<FlyObject> bulletList, ArrayList<FlyObject> EnemyBulletList, Graphics g, PlayFigther player) {
this.flyList = flyList;
this.bulletList = bulletList;
this.g = g;
this.EnemyBulletList=EnemyBulletList;
this.player = player;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//图片加载
BufferedImage Buffimg = new BufferedImage(GameData.FrameWidth, GameData.FrameHeight,BufferedImage.TYPE_INT_ARGB);
Graphics bg = Buffimg.getGraphics();
//背景
bg.setColor(new Color(238,238,238));
bg.fillRect(0,0,1000,800);
bg.drawImage(image,0,0, GameData.FrameWidth, GameData.FrameHeight,null);
//玩家
player.draw(bg);
player.move();
//敌人飞机队列
for (int i = 0; i < flyList.size(); i++) {
FlyObject fly = flyList.get(i);
fly.draw(bg);
fly.move();
}
//子弹队列
for (int i = 0; i < bulletList.size(); i++) {
FlyObject fly = bulletList.get(i);
fly.move();
fly.draw(bg);
}
for (int i = 0; i < EnemyBulletList.size(); i++) {
FlyObject fly = EnemyBulletList.get(i);
fly.move();
fly.draw(bg);
}
//绘制积分:
bg.setFont(new Font("黑体",Font.BOLD,20));
bg.setColor(Color.CYAN);
String msg = "Game score-"+player.score;
bg.drawString(msg,10,80);
g.drawImage(Buffimg,0,0,null);
//检测碰撞
for (int i = 0; i < bulletList.size(); i++) {
FlyObject bullet = bulletList.get(i);
for (int j = 0; j < flyList.size(); j++) {
FlyObject enemyfighter = flyList.get(j);
if(bullet.isCollsion(enemyfighter)){
bullet.setIslive(false);
enemyfighter.setIslive(false);
player.score++;
break;
}
}
}
for (int i = 0; i < EnemyBulletList.size(); i++) {
FlyObject enemyBullet = EnemyBulletList.get(i);
if(player.isCollsion(enemyBullet)){
player.score--;
enemyBullet.setIslive(false);
System.out.println("score -1");
if(player.score<=0){
player.setIslive(false);
//后续开发结束
}
}
}
}
}
}
难点:①绘制功能实现:这里使用缓冲图片,游戏加载起来图片就不会有闪烁。
背景图、计分板
总结:该游戏——飞机大战 只是实现核心功能,后续还可以添加关卡、道具、不同种类的飞机、子弹等等,更多的是应该通过这个运行模式去理解多线程,就像我们现在实现一个其他游戏都是通过实现这个游戏的思想。