用Java写一个简易飞机大战

一.构思

首先我们应明确基本思路,飞机大战需要:

  1. 一个UI类做展示面板;

  1. 一个监听器类实现键盘控制;

  1. 一个飞行对象类作为父类,写入基本字段和方法;

  1. 四个飞行对象子类继承父类,分别是:玩家飞机、敌方飞机、玩家飞机子弹、敌方飞机子弹;

  1. 三个线程类,分别是:主线程、自动生成敌机线程、自动生成敌机子弹线程;

  1. 一个接口写入游戏基本数据。

二.游戏数据

public interface GameData {
        //窗体大小
        int FrameHeight=900;
        int FrameWidth=700;
        //敌机大小
        int EnemyWidth = 80;
        int EnemyHeight = 100;
        //分数位置
        int Fontx = 100;
        int Fonty = 100;
        //玩家战机初始坐标
        int PlayerX = 350;
        int PlayerY = 750;
        //玩家战机大小
        int PlayerWidth = 130;
        int PlayerHeight = 150;
        //玩家子弹大小
        int PBW = 20;
        int PBH = 30;
        //敌机子弹大小
        int EBW = 10;
        int EBH = 20;
}

三.UI面板

public class UI extends JFrame implements GameData{
    public void showUI() {
        setTitle("飞机大战");//设置窗体名字
        setSize(FrameWidth,FrameHeight);//设置窗体大小
        setLocationRelativeTo(null);//设置窗体居中显示
        setDefaultCloseOperation(3);//设置窗体后台自动关闭
        
        setVisible(true);
    }
    public static void main(String[]args) {
        new UI().showUI();
    }
}

四.飞行对象类的创建和继承

1.父类:

一个飞行对象应有以下属性和方法:

属性:初始坐标x、y;大小width、height;速度speedx、speedy;存活状态alive。

方法:绘制自己、移动、开火、检测碰撞。

public class FlyerObject {
    private int x,y;//飞行对象的坐标
    private int width,height;//飞行对象的大小
    private int speedx,speedy;//飞行对象的速度
    private boolean alive = true;//飞行对象是否存活,布尔值默认为false
    //构造方法
    public FlyerObject(int x,int y,int width,int height,int speedx,int speedy) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        this.speedx = speedx;
        this.speedy = speedy;
    }
    //绘制飞行对象
    public void draw(Graphics g) {
        
    }
    //移动
    public void move() {
        //先判定是否存活再移动
        if(getalive(){
            x += speedx;
            y += speedy;
        }
    }
    //开火
    public void fire() {
        
    }
    //检测碰撞
    public void collide() {
        
    }
    //相应的set和get方法
    public void setalive(Boolean alive) {
        this.alive = alive;
    }
    public boolean getalive() {
        return alive;
    }
    public void setx(int x) {
        this.x = x;
    }
    public int getx() {
        return x;
    }
    public void sety(int y) {
        this.y = y;
    }
    public int gety() {
        return y;
    }
    public void setwidth(int width) {
        this.width = width;
    }
    public int getwidth() {
        return width;
    }
    public void setheight(int height) {
        this.height = height;
    }
    public int getheight() {
        return height;
    }
    public void setspeedx(int speedx) {
        this.speedx = speedx;
    }
    public int getspeedx() {
        return speedx;
    }
    public void setspeedy(int speedy) {
        this.speedy = speedy;
    }
    public int getspeedy() {
        return speedy;
    }
}

2.玩家类:

玩家类除了要重写父类的绘制、移动、开火方法外,由于我们需要用键盘操控飞机移动,所以玩家类中还要写入上下左右移动的方法,因为父类中的move()方法是采用位置+速度的方式实现的,所以玩家类中的上下左右移动方法只需改变速度即可。

由于玩家飞机对象只需要一个即可,所以我们可以在玩家类中写一个静态方法,返回一个已经设置好数据的玩家飞机对象,便于外部类使用。

public class PlayerObject extends FlyerObject implements GameData{
    int score = 100;//玩家分数
    //构造方法
    public PlayerObject(int x, int y, int width, int height, int speedx, int speedy) {
        super(x, y, width, height, speedx, speedy);
    }
    //玩家飞机图片
    static Image img = new ImageIcon("Image/飞机大战素材2.png").getImage();
    //该静态方法用于外部直接获取player对象
    public static PlayerObject getnewPlayer() {
        return new PlayerObject(PlayerX,PlayerY,PlayerWidth,PlayerHeight,0,0);
        //返回一个已经设置好数据的玩家飞机对象
    }
    //绘制玩家飞机
    public void draw(Graphics g) {
        if(getalive()){
            g.drawImage(img, getx(), gety(),getwidth(),getheight(), null);
        }
    }
    //移动
    public void move() {
        super.move();
        if(getalive()){
            //进行边界判定
            if(getx()<=0) {
                setx(0);
            }
            if(getx()+PlayerWidth>=FrameWidth) {
                setx(FrameWidth-PlayerWidth);
            }
            if(gety()<=0) {
                sety(0);
            }
            if(gety()+PlayerHeight>=FrameHeight) {
                sety(FrameHeight-PlayerHeight);
            }
        }
    }
    //上移
    public void up() {
        setspeedy(-20);
    }
    //下移
    public void down() {
        setspeedy(20);
    }
    //左移
    public void left() {
        setspeedx(-20);
    }
    //右移
    public void right() {
        setspeedx(20);
    }
    //开火
    public void fire() {
        
    }
}

3.敌机类:

由于敌机是自动移动的,所以敌机的move方法要加上撞到边界会反弹的代码。

public class EnemyObject extends FlyerObject implements GameData{
    //构造方法
    public EnemyObject(int x, int y, int width, int height, int speedx, int speedy) {
        super(x, y, width, height, speedx, speedy);
    }
    
    //敌机图片,注意此处只能把图片放入Image包中再填相对路径,否则后面绘制缓冲区会失败
    static Image img = new ImageIcon("Image/飞机大战素材-removebg-preview.png").getImage();
    //绘制
    public void draw(Graphics g) {
        if(getalive()){  
            g.drawImage(img, getx(), gety(),getwidth(),getheight(), null);
        }
    }
    //移动
    public void move() {
        super.move();
        if(getailve()){
            if(getx()<=0||getx()+EnemyWidth>=FrameWidth) {
                setspeedx(-getspeedx());//撞到边界速度取反
            }
        }
    }
}

4.玩家子弹类:

public class PBulletObject extends FlyerObject{

    public PBulletObject(int x, int y, int width, int height, int speedx, int speedy) {
        super(x, y, width, height, speedx, speedy);
    }
    //绘制
    public void draw(Graphics g) {
        g.setColor(Color.BLUE);
        if(getalive()){
            g.fillOval(getx(), gety(), getwidth(), getheight());
        }
    }
    //移动
    public void move() {
        if(getalive()){
            sety(gety()-getspeedy());
        }
    }
}

5.敌机子弹类:

public class EBulletObject extends FlyerObject{

    public EBulletObject(int x, int y, int width, int height, int speedx, int speedy) {
        super(x, y, width, height, speedx, speedy);
    }
    //绘制
    public void draw(Graphics g) {
        if(getalive()){
            g.setColor(Color.WHITE);
            g.fillOval(getx(), gety(), getwidth(), getheight());
        }
    }
    //移动
    public void move() {
        if(getalive()){
            sety(gety()+getspeedy());
        }
    }
}

五.数组的创建和监听器的添加

该游戏一共需要三个数组,分别是:玩家子弹数组、敌机数组、敌机子弹数组,由于这三个数组需要在线程中用到,所以我们将三个数组的创建、监听器的添加、三个线程的运行都写在UI类中。

该游戏的监听器采用KeyListener,因为玩家需要操控飞机对象移动和开火,因此要往KeyListener中传入玩家飞机对象和玩家子弹数组,在监听器类中添加set()方法。

1.在UI类中:

创建数组:

    Listener ls = new Listener();
    ArrayList<PBulletObject> pbulletlist = new ArrayList<>();//玩家子弹数组
    ArrayList<EBulletObject> ebulletlist = new ArrayList<>();//敌机子弹数组
    ArrayList<EnemyObject> elist = new ArrayList<>();//敌机数组
    PlayerObject player = PlayerObject.getnewPlayer();//取出玩家飞机对象,用于传入监听器类
    public void showUI() {
    
    

添加监听器:

        setVisible(true);
        
        addKeyListener(ls);//添加监听器
        ls.setplayer(player);//传入玩家飞机对象
        ls.setlist(pbulletlist);//传入玩家子弹数组

2.在监听器类中:

因为开火的操作需要用到子弹数组,所以先往飞行对象类中的fire()方法填入参数。

//开火
    public void fire(ArrayList<PBulletObject> pbulletlist) {
    }
public class Listener implements KeyListener{
    PlayerObject player;//玩家对象
    ArrayList<PBulletObject> pbulletlist;//玩家子弹数组
    public void setplayer(PlayerObject player) {
        this.player = player;
    }
    public void setlist(ArrayList<PBulletObject> pbulletlist) {
        this.pbulletlist = pbulletlist;
    }
    
    public void keyTyped(KeyEvent e) {
        
    }

    
    public void keyPressed(KeyEvent e) {
        //对应键位添加对应操作
        switch(e.getKeyCode()) {
            case KeyEvent.VK_W:
                player.up();
                break;
            case KeyEvent.VK_S:
                player.down();
                break;
            case KeyEvent.VK_A:
                player.left();
                break;
            case KeyEvent.VK_D:
                player.right();
                break;
            case KeyEvent.VK_SPACE:
                player.fire(pbulletlist);
        }
    }

    
    public void keyReleased(KeyEvent e) {
        //松手时玩家飞机停止
        player.setspeedx(0);
        player.setspeedy(0);
    }

}

最后在玩家飞机类中把fire()方法补全

//开火
    public void fire(ArrayList<PBulletObject> pbulletlist) {
        if(getalive()) {
            PBulletObject bullet = new PBulletObject(getx()+PlayerWidth/2,gety(),EBW,EBH,0,10);
            pbulletlist.add(bullet);
        }
    }

六.线程的添加

1.自动生成敌机的线程:

往该线程中传入敌机数组,线程运行时,不断往数组中添加新的敌机对象,同时取随机数作为新敌机的数据。

public class EnemyThread extends Thread implements GameData{
    ArrayList<EnemyObject> elist;//敌机数组
    Random ran = new Random();//用于取随机数
    //构造方法
    public EnemyThread(ArrayList<EnemyObject> elist) {
        this.elist = elist;
    }
    public void run() {
        while(true) {
            //每隔两秒生成一架敌机
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            int x = ran.nextInt(FrameWidth)-EnemyWidth;//敌机初始x坐标在边框内
            int y = ran.nextInt(FrameHeight/2);//敌机初始y坐标在边框上半部分
            int speedx = ran.nextInt(10)-5;//敌机水平方向可以左右移动
            int speedy = ran.nextInt(10)+1;//敌机竖直方向只能向下移动
            //对x坐标进行设定,防止敌机撞到边界不断抽搐
            if(x<0) {
                x=0;
                if(speedx<0) {
                    speedx=ran.nextInt(10);
                }
            }
            EnemyObject enemy = new EnemyObject(x,y,EnemyWidth,EnemyHeight,speedx,speedy);
            elist.add(enemy);//往敌机数组中添加敌机对象
        }
    }
}

2.自动生成敌机子弹的线程:

往该线程中传入敌机数组和敌机子弹数组,挨个取出敌机数组,取其坐标作为敌机子弹数组,挨个往敌机子弹数组中添加敌机子弹。

public class EBulletThread extends Thread implements GameData{
    ArrayList<EnemyObject> elist;//敌机数组
    ArrayList<EBulletObject> ebulletlist;//敌机子弹数组
    Random ran = new Random();//随机数用于生成子弹的时间
    public EBulletThread(ArrayList<EnemyObject> elist,ArrayList<EBulletObject> ebulletlist) {
        this.elist = elist;
        this.ebulletlist = ebulletlist;
    }
    public void run() {
        while(true) {
            try {
                Thread.sleep(ran.nextInt(500)+500);//每过随机时间生成子弹
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //取出敌机
            for(int i=0;i<elist.size();i++) {
                EnemyObject enemy = elist.get(i);
                //判断存活
                if(enemy.getalive()) {
                    //获取坐标
                    int x = enemy.getx();
                    int y = enemy.gety();
                    EBulletObject bullet = new EBulletObject(x+EnemyWidth/2,y+EnemyHeight,EBW,EBH,0,10);
                    ebulletlist.add(bullet);//往敌机子弹数组中添加敌机子弹
                }
            }
        }
    }
}

3.主线程:

主线程中要完成一下任务:

绘制背景;

绘制玩家飞机、玩家子弹、敌机、敌机子弹;

绘制玩家分数;

检测碰撞。

(上述有关绘制的操作均在缓冲区进行,以消除闪烁)

在这之前要回飞行对象类补全检测碰撞的方法:

//检测碰撞
    public boolean collide(FlyerObject flyer) {
        if(flyer==null) {
            return false;
        }
        //要双方都存活才能发生碰撞
        if(getalive()&&flyer.getalive()) {
            if(this.x+this.width>flyer.getx()&&this.x<flyer.getx()+flyer.getwidth()&&this.y+this.height>flyer.gety()&&this.y<flyer.gety()+flyer.getheight()) {
                return true;//发生碰撞,返回true
            }
        }
        return false;
    }

主线程:

由上述可知需要往主线程中传入:玩家对象、玩家子弹数组、敌机数组、敌机子弹数组、画笔。

public class MainThread extends Thread implements GameData{
    private PlayerObject player;//玩家对象
    private ArrayList<PBulletObject> pbulletlist;//玩家子弹数组
    private ArrayList<EnemyObject> elist;//敌机数组
    private ArrayList<EBulletObject> ebulletlist;//敌机子弹数组
    private Graphics g;//画笔
    public MainThread(PlayerObject player,ArrayList<PBulletObject> pbulletlist,ArrayList<EnemyObject> elist,
            ArrayList<EBulletObject> ebulletlist,Graphics g) {
        this.player = player;
        this.pbulletlist = pbulletlist;
        this.elist = elist;
        this.ebulletlist = ebulletlist;
        this.g = g;
    }
    public void run() {
        while(true) {
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //创建缓冲区
            BufferedImage buff = new BufferedImage(FrameWidth,FrameHeight,BufferedImage.TYPE_INT_ARGB);//缓冲区
            Graphics bg = buff.getGraphics();//缓冲区的画笔
            
            //绘制背景
            bg.drawImage(new ImageIcon("Image/apic5877.jpg").getImage()
                    ,0,0,FrameWidth,FrameHeight,null);
            
            //绘制玩家飞机
            player.draw(bg);
            player.move();
            
            //绘制玩家子弹
            for(int i=0;i<pbulletlist.size();i++) {
                PBulletObject bullet = pbulletlist.get(i);
                bullet.draw(bg);
                bullet.move();
            }
            
            //绘制敌机
            for(int i=0;i<elist.size();i++) {
                EnemyObject enemy = elist.get(i);
                enemy.draw(bg);
                enemy.move();
            }
            
            //绘制敌机子弹
            for(int i=0;i<ebulletlist.size();i++) {
                EBulletObject bullet = ebulletlist.get(i);
                bullet.draw(bg);
                bullet.move();
            }
            
            //绘制分数面板
            bg.setFont(new Font("黑体",Font.BOLD,20));//设置字体样式
            bg.setColor(Color.BLACK);
            bg.drawString("分数:"+player.score, Fontx, Fonty);
            
            g.drawImage(buff, 0, 0, null);//绘制缓冲区
            
            //检测碰撞
            //玩家子弹碰敌机
            for(int i=0;i<pbulletlist.size();i++) {
                PBulletObject bullet = pbulletlist.get(i);//取出一颗玩家子弹,依次与敌机检测碰撞
                for(int j=0;j<elist.size();j++) {
                    EnemyObject enemy = elist.get(j);
                    if(bullet.collide(enemy)) {//若发生碰撞,将两飞行物设为不存活
                        bullet.setalive(false);
                        enemy.setalive(false);
                        player.score++;//玩家分数增加
                        break;
                    }
                }
            }
            //玩家碰敌机子弹
            for(int i=0;i<ebulletlist.size();i++) {
                EBulletObject bullet = ebulletlist.get(i);
                if(player.collide(bullet)) {
                    bullet.setalive(false);
                    player.score--;
                    if(player.score<=0) {
                        player.setalive(false);
                    }
                }
            }
        }
    }
}

最后在UI类中创建并启动线程。

        ls.setlist(pbulletlist);//传入玩家子弹数组
        //主线程
        MainThread thread1 = new MainThread(player,pbulletlist,elist,ebulletlist,getGraphics());
        thread1.start();
        //敌机线程
        EnemyThread thread2 = new EnemyThread(elist);
        thread2.start();
        //敌机子弹线程
        EBulletThread thread3 = new EBulletThread(elist,ebulletlist);
        thread3.start();
    }

猜你喜欢

转载自blog.csdn.net/m0_73249076/article/details/129219392