多线程——飞机大战

一、结构框架与思路

运用多线程实现的飞机大战:

①ThreadGame 主线程 实现1、绘制 角色 背景 2、移动 3、检测碰撞

②AutoThreadFly 实现1、间隔生成敌机对象,并将敌机对象存入一个List中

③AutoBullet 实现1、遍历敌机的List 取出每一个敌机对象,利用其坐标生成一颗子弹,加入子弹List中

④监听器线程 实现1、修改玩家的坐标 2、生成玩家子弹加入玩家子弹List中

二、图片展示

三、代码实现

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);
                        //后续开发结束
                    }
                }
            }
        }
    }
}

难点:①绘制功能实现:这里使用缓冲图片,游戏加载起来图片就不会有闪烁。

背景图、计分板

总结:该游戏——飞机大战 只是实现核心功能,后续还可以添加关卡、道具、不同种类的飞机、子弹等等,更多的是应该通过这个运行模式去理解多线程,就像我们现在实现一个其他游戏都是通过实现这个游戏的思想。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值