一篇文章手把手教你如何从0开始搞定一个飞机大战小游戏,目前全站最细,素材简单免费(Java版)

(一)实际效果:

话不多说,先看视频:

飞机大战演示

效果截图:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

如下为本项目所需要的照片素材:在这里插入图片描述

小提醒:CSDN上的素材都是需要提取码解压的,非常不方便,因此各位同学可以上百度自己截图,但不推荐用豆包生成图片(豆包生成图片往往不对称大小不合适)。
截出来的图如果有白边,则可以上https://www.remove.bg/zh,,这是一个可以将截图的白色背景去掉,以便我们使照片主体融入背景图的网站

以及此项目所创建的类:

在这里插入图片描述

好,话不多说,上!代!码!

(二)代码:

(预先准备)首先创建一个图片素材类

package com11.fjm0601;

import java.awt.*;

public class GameWDUtils {
    public static Image bgObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\背景图.png");
    //ToolKit是一个抽象类,里面有好多方法可以与系统进行交互,例如getDefaultToolkit()方法就是获得一个ToolKit实例,然后里面又有一个方法也就是getImage(),这个就可以获取系统资源中的图片
    public static Image planeObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\我方飞机.png");

    public static Image bossPlane1Obj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\敌方boss1.png");

    public static Image bossPlane2Obj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\敌方boss2.png");

    public static Image SelfBulletObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\SelfBulletObj.png");

    public static Image BossBulletObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\bossBulletObj.png");

    public static Image SpeedChangeObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\强化速度果实.png");

    public static Image AddBulletObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\强化分叉果实.png");

    public static Image EnemyPlanetObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\敌方普通飞机.png");

    public static Image boomObj= Toolkit.getDefaultToolkit().getImage("C:\\Users\\A1738\\Desktop\\飞机大战素材包\\爆炸特效.png");

}

(1)创建一个600×800的窗口:

java
package com11.fjm0601;


import javax.swing.*;
import java.awt.*;

public class GameWD extends JFrame {
    public void showUI(){
        setTitle("飞机大战");
        setLayout(new BorderLayout());
        setSize(600,800);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);

    }

    public static void main(String[] args) {
        new GameWD().showUI();
    }
}

效果展示:

在这里插入图片描述

思路规划:

1. 游戏分三个状态:游戏加载 游戏进行 游戏结束,分别用state=0 state=1 state=2 来指代,因此在主类中创建变量state。
2. 游戏加载 和 游戏进行 这两个部分都是需要不断进行重绘的,因此在showUI方法中要加入repiant方法。并且为了方便期间,把游戏结束这个状态也并入重绘方法中。
package com11.fjm0601;


import javax.swing.*;
import java.awt.*;

public class GameWD extends JFrame {
*    int state=0;
    
    public void showUI(){
        setTitle("飞机大战");
        setLayout(new BorderLayout());
        setSize(600,800);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setVisible(true);
*         while (true){

*           repaint();
*          try {
*             Thread.sleep(30);//间隔30毫秒再画一次,这也就是我们说的帧率
*        } catch (InterruptedException e) {
*                throw new Run*timeException(e);
*           }
*      }
        

    }
*    public void paint(Graphics g){
 
*       if(state==0){

*       }else if(state==1){
  
*       }else if(state==2){

*      }
* }

    public static void main(String[] args) {
        new GameWD().showUI();
    }
}

(2)创建游戏加载页面

2.1 创建父类

由于飞机大战的元素特征具有高度重合性(无论是子弹,背景,我方飞机和敌方飞机,都具有速度,大小,图片,坐标)。因此可以考虑把特征整合到一起,来创建一个父类
这个父类GameObj成员变量构造方法绘制自身的方法组成

package com11.fjm0601;

import java.awt.*;

public class GameObj {
    Image image;
    int x;
    int y;
    int width;
    int height;
    double speed;
    GameWD frame;
    public GameObj(Image image,int x,int y,int width,int height,double speed,GameWD frame){
        this.image=image;
        this.x=x;
        this.y=y;
        this.width=width;
        this.height=height;
        this.speed=speed;
        this.frame=frame;
    }
    public void paintSelf(Graphics graphics){
        graphics.drawImage(image,x,y,null);
    }
    public Rectangle getRec(){
        return new Rectangle(x,y,width,height);//方便后续进行碰撞检测
    }
}
2.2 创建背景图类
package com11.fjm0601;

import java.awt.*;

public class BackGroundObj extends GameObj {
    public BackGroundObj(Image image, int x, int y, int width, int height, double speed, GameWD frame) {
        super(image, x, y, width, height, speed, frame);
    }

    @Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
       
    }

    @Override
    public Rectangle getRec() {
        return super.getRec();
    }
}

小诀窍:重写父类方法时,我们可以通过右击->“Generate”->“Override Methods”->按住Ctrl,,用鼠标点击父类的内容->"OK"即可,这样提高了重写效率

2.3 添加背景图

如果直接绘制背景图,则会出现频闪,观感不佳,因此在这里引入缓存
频闪成因:由于图片中各元素的绘制顺序不同,导致图片总是不同时产生,所以出现频闪
缓存:创建一个空白图片,先将要画的部分绘制到这个图片里,再把绘制好的图片画到窗口中

     int state=0;
*    Image BaseBoard=null;
......
    public void paint(Graphics g){
*        BaseBoard=createImage(getWidth(),getHeight());//创建一个空白Image,宽高与本窗口对齐
*       Graphics gImage=BaseBoard.getGraphics();//获取这个空白图片的画笔
        if(state==0){
*            gImage.drawImage(GameWDUtils.bgObj,0,0,null);
* //使用这个图片的画笔绘制
* //从bgOBj这个图片的左上角(0,0)开始画

        }else if(state==1){

        }else if(state==2){

        }
 *       g.drawImage(BaseBoard,0,0,null);
    }

效果展示:

在这里插入图片描述

2.4 实现 游戏加载进程 进程结束后出现按钮 两功能
    int state=0;
    Image BaseBoard=null;
*    JButton startBtn=new JButton("开始游戏");
*   int load=0;
......
   if(state==0){
            gImage.drawImage(GameWDUtils.bgObj,0,0,null);
*            if(load<100){
*               gImage.setColor(Color.WHITE);
*               gImage.setFont(new Font("幼圆",Font.BOLD,20));//(字体 加粗 大小)
*               gImage.drawString("游戏正在加载:"+load+"%",205,200);
*               gImage.drawRect(150,230,300,30);
*               gImage.fillRect(150,230,load*3,30);
*               load++;
*     }else if(load==100){
*           gImage.setColor(Color.WHITE);
*           gImage.setFont(new Font("幼圆",Font.BOLD,20));
*           gImage.drawString("游戏加载完成",230,200);
*           startBtn.setBounds(190,230,200,40);(x,y,width,height)
* //这里与先前主界面的setLayout(new BorderLayout())呼应,只有把主界面设置成边框布局,才能设置自己想要的按钮的位置。(原先的JFrame默认是流式布局)
* //不推荐使用startBtn.setPreferredSize(200,40)+startBtn.setLoacation(190,230)方法,因为实际上显示不出来
*           startBtn.addActionListener(new ActionListener() {
*         @Override
*        public void actionPerformed(ActionEvent e) {
*                       String str=e.getActionCommand();
*                       if(str.equals("开始游戏")){
*                         state=1;
*                          remove(startBtn);//在游戏进行中,不能出现按钮这个东西
*         }
*   }
*});
*                add(startBtn);
*                startBtn.repaint();//需要在这里加入repaint()方法,不然添加进去马上就被新重绘的图片覆盖掉显示不出来了
            }

        }

最终效果:

游戏加载演示

(3)进入游戏中,将背景图后移,实现飞机向前飞行的效果:

3.1在背景图的paintSelf方法中,加入循环移动。
@Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
 *      if(y<0){
 *           y+=speed;//如果背景图左上角的y坐标小于窗口的0坐标,则背景图向下以speed的速度更新
 *       }else {
 *           y=-2200;//如果背景图左上角的y移到窗口最上边的界限以下,则将背景图重置到起始位置,这里之所以是-2200,是因为窗口高度为800,我的素材背景图长为3000,因此背景图左上角的初始位置为800-3000=-2200。
 *       }
    }

用图来展示我的背景图(蓝)和窗口(红)的初始位置关系:我的背景图的长宽(708×3072)都比窗口(600×800)要大,因此想要让背景图向下移动,则需要让背景图一开始就在窗口上方
在这里插入图片描述
等到图片左上角的y值小于窗口最上边界限,则重置背景图位置,如果不重置位置,那么图将移出窗口,窗口将变得空无一物。
在这里插入图片描述
为什么重复提及左上角这个概念呢,这与g.drawImage(image,x,y.null)本身的参数有关,x,y既是这个图片的左上角在窗口中的坐标。这行代码就意味着从窗口的(x,y)坐标处开始画这个图片

3.2 在主类中添加objectLIst,向objeList中加入创建好的bgObj背景图对象;并在左上角加入获取分数提示词
     int load=0;
*    int Score=0;
*    BackGroundObj bgObj=new BackGroundObj(com11.fjm0601.GameWDUtils.bgObj,0,-2200,708,3072,5,this);
*    List<com11.fjm0601.GameObj> objectList=new ArrayList<>();
......
public void showUI(){
*       objectList.add(bgObj);
        setTitle("飞机大战");
        setLayout(new BorderLayout());
......
      else if(state==1){
*            for(GameObj object:objectList){
*                object.paintSelf(gImage);
*            }
*            gImage.setColor(Color.CYAN);
*            gImage.setFont(new Font("幼圆",Font.BOLD,20));
*            gImage.drawString("获得分数"+" "+Score,30,70);
        }        

    }

实现效果:

进入游戏背景循环图演示

(四)敌方普通飞机+boss+我方飞机显现

4.1 加入敌方普通飞机

原理:根据画面绘制次率,以一定频率绘制敌机(即敌机产生速度)。并且通过将敌机加入集合的方式,实现敌机的重复产生

    int load=0;
    int Score=0;
*    int enemyPlanex=0;//敌方飞机左上角的横坐标
*    int flushCount=0;//画面绘制次数
......
 while (true){

*       enemyPlanex=(int)(Math.random()*9)*60;//Math.random()*9就是随机产生0~9之间的数字,之所以*60是因为,敌方飞机图片的长为60,窗口的宽为600,因此能并排容纳10个飞机。(0~9)*60,就是一个随机飞机的左上角坐标
*            if(  state==1 && flushCount % 20 == 0){//必须要state==1,不然敌机从state==0就开始产生了,这里设置成每隔20次就产生一架飞机
*                creatEnemyPlaneObj();
*            }
            repaint();
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
*            flushCount++;//记录画面绘制次数

        }
...... 
*    public void creatEnemyPlaneObj(){
*        com11.fjm0601.EnemyPlaneObj enemyPlaneObj=new EnemyPlaneObj(GameWDUtils.EnemyPlanetObj,enemyPlanex,0,60,42,5,this);//this就是指代当前的GameWD
*        objectList.add(enemyPlaneObj);
*    }

    public static void main(String[] args) {
        new GameWD().showUI();
    }       
4.2 加入我方飞机,并实现鼠标对飞机移动的控制
(1)创建我方飞机类
```java
package com11.fjm0601;

import java.awt.*;


public class PlaneObj extends GameObj{

    public PlaneObj(Image image, int x, int y, int width, int height, double speed, GameWD frame) {
        super(image, x, y, width, height, speed, frame);

    }

    @Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);

    }

    @Override
    public Rectangle getRec() {
        return super.getRec();
    }
}
(2)主界面加入监听器
     BackGroundObj bgObj=new BackGroundObj(com11.fjm0601.GameWDUtils.bgObj,0,-2200,708,3072,5,this);
*    PlaneObj planeObj=new PlaneObj(com11.fjm0601.GameWDUtils.planeObj,0,0,34,34,0,this);
*    MouseMotionListener ml=new MouseAdapter() {
*       @Override
*      public void mouseMoved(MouseEvent e) {
*         planeObj.x=e.getX()-17;
*         planeObj.y=e.getY()-17;//这两行是为了
*   }
*};
//先创建飞机,再创建监听器对象,对飞机的坐标进行修改。最后对窗口添加监听器
     List<com11.fjm0601.GameObj> objectList=new ArrayList<>();

public void showUI(){
*        addMouseMotionListener(ml);
         objectList.add(bgObj);
*        objectList.add(planeObj);//注意这里的顺序,必须要先添加背景图,再添加飞机。不然飞机会被背景图覆盖掉
        setTitle("飞机大战");
4.3 加入敌方boss
(1)创建敌方boss类,并加入边界碰撞检测
package com11.fjm0601;

import java.awt.*;

public class BossPLaneObj extends  GameObj{
    public BossPLaneObj(Image image, int x, int y, int width, int height, double speed, GameWD frame) {
        super(image, x, y, width, height, speed, frame);
    }

    @Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        if(0<x && x+width<600){
            x+=speed;
        }else if(x<=0 || x+width>=600){
            speed=-speed;
            x+=speed;
        }
    }

    @Override
    public Rectangle getRec() {
        return super.getRec();
    }
}
(2)创建creatBossObj方法,实现两种敌机的循环产生
     int flushCount=0;
*    int boss1or2=1;//初始化为1,第一次的飞机就是bossPlane1
*    BossPLaneObj bopObj=null;
* 
......

if(  state==1 && flushCount % 20 == 0){
                creatEnemyPlaneObj();
            }
*            if( state ==1 && bopObj==null){
*                creatBossPlaneObj();
*            }
            repaint();
            try {
                Thread.sleep(30);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

...... 
 public void creatEnemyPlaneObj(){
        com11.fjm0601.EnemyPlaneObj enemyPlaneObj=new EnemyPlaneObj(GameWDUtils.EnemyPlanetObj,enemyPlanex,0,60,42,5,this);
        objectList.add(enemyPlaneObj);
    }          
*           public void creatBossPlaneObj(){
*        int bossX=(int)(Math.random()*425);//最

*             if(boss1or2==1){
*            bopObj=new BossPLaneObj(GameWDUtils.bossPlane1Obj,bossX,20,139,104,5,this);
*            objectList.add(bopObj);
*            boss1or2=2;
*        }else if(boss1or2==2){
*             bopObj=new BossPLaneObj(GameWDUtils.bossPlane2Obj,bossX,20,172,129,5,this);
*            objectList.add(bopObj);
*            boss1or2=1;
*        }

*    }

最终效果:

敌方普飞机+我方飞机+敌方boss

(五)敌方boss和我方子弹添加

5.1 创建敌方和我方子弹类:

敌方子弹

package com11.fjm0601;

import java.awt.*;

public class BossShellObj extends GameObj{
    public BossShellObj(Image image, int x, int y, int width, int height, double speed, GameWD frame) {
        super(image, x, y, width, height, speed, frame);
    }

    @Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;//子弹以speed的速度向下飞
    }

    @Override
    public Rectangle getRec() {
        return super.getRec();
    }
}

我方子弹:

package com11.fjm0601;

import java.awt.*;

public class ShellObj extends  GameObj{
    @Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y-=speed;//我方子弹以speed的速度向上移动

    }

    public ShellObj(Image image, int x, int y, int width, int height, double speed, GameWD frame) {
        super(image, x, y, width, height, speed, frame);
    }

    @Override
    public Rectangle getRec() {
        return super.getRec();
    }
5.2 在主类里面添加 产生子弹的方法:
            if( state ==1 && bopObj==null){
                creatBossPlaneObj();
            }
*            if(state ==1 && planeObj!=null  && flushCount % 10==0){//这实质上是我方子弹的产生速度
*               creatShellObj();
*            }
*            if(state ==1 && bopObj!=null  && flushCount % 20==0){//这实质上是产生敌方子弹的速度
*                creatBossShellObj();
*            }
            repaint();

......

    public void creatEnemyPlaneObj(){
        com11.fjm0601.EnemyPlaneObj enemyPlaneObj=new EnemyPlaneObj(GameWDUtils.EnemyPlanetObj,enemyPlanex,0,60,42,5,this);
        objectList.add(enemyPlaneObj);
    }
  
*    public void creatShellObj(){
*        ShellObj shellObj=new ShellObj(GameWDUtils.SelfBulletObj,planeObj.x+17,planeObj.y,10,9,5,this);
*        objectList.add(shellObj);
*    }
*    public void creatBossShellObj(){
*        BossShellObj shellObj=new BossShellObj(GameWDUtils.BossBulletObj,bopObj.x+bopObj.width/2,bopObj.y+bopObj.height,13,12,5,this);
*        objectList.add(shellObj);
*    }

最终效果:

敌机子弹和我方子弹演示

(六)加入碰撞检测,击毁特效

注意:由于我们采用objectList遍历元素并绘制,如果在其他的线程中对本集合进行修改,则会抛出concurrentModificationException异常,这个异常会在“遍历集合的同时又修改集合”的时候出现。详情请见笔者的另一篇介绍该异常的博客(还没写,后续补上{流汗)。因此我们在碰撞检测中先将要删除的元素放入一个集合中,之后在遍历集合前统一删除,这样就能避免该异常。

6.1 在主类中创建一个removeLIst集合,在遍历objectList前先将removeList集合清除掉,然后将removeList集合元素清空
     List<com11.fjm0601.GameObj> objectList=new ArrayList<>();
*    List<GameObj> removeList=new ArrayList<>();
 
......

else if(state==1){
*            objectList.removeAll(removeList);
*            removeList.clear();

            for(GameObj object:objectList){
                object.paintSelf(gImage);
            }
6.2 在其他类中加入碰撞检测,前期写好的getRec用以获取元素位置
(1)敌机 与 我方子弹:

敌机类中:

public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;
*        for(GameObj  shellObj: this.frame.objectList){
*            if(shellObj instanceof  ShellObj){
*                if(shellObj.getRec().intersects(this.getRec())){
*                    graphics.drawImage(GameWDUtils.boomObj,this.x,this.y,null);
*                    this.frame.removeList.add(shellObj);
*                    this.frame.removeList.add(this);
*                    this.frame.Score++;
*                }
*            }
*        }
    }
(2)敌方boss子弹 与 我方飞机

敌方bossShellObj类中:

public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;
*        if(  this.getRec().intersects(this.frame.planeObj.getRec())){
*            this.frame.removeList.add(this);
*            this.frame.state=2;
*       }
 
 }

主类中:

     JButton startBtn=new JButton("开始游戏");
*    JButton restartBtn=new JButton("重新开始游戏");
* 
.....
    else if(state==2){
*            gImage.setColor(Color.BLACK);
*            gImage.fillRect(0,0,getWidth(),getHeight());
*            gImage.setColor(Color.RED);
*            gImage.setFont(new Font("幼圆",Font.BOLD,40));
*            gImage.drawString("Game Over",200,200);
*            restartBtn.setBounds(190,230,200,40);
*            restartBtn.addActionListener(new ActionListener() {
*                @Override
*                public void actionPerformed(ActionEvent e) {
*                    String *str=e.getActionCommand();
*                    if(str.equals("重新开始游戏")){
*                        state=1;
*                        remove(restartBtn);
*                    }
*                }
*            });
*            add(restartBtn);

*            restartBtn.repaint();
        }
(3)敌方飞机 与 我方飞机

敌方飞机类中:

public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;
        for(GameObj  shellObj: this.frame.objectList){
            if(shellObj instanceof  ShellObj){
                if(shellObj.getRec().intersects(this.getRec())){
                    graphics.drawImage(GameWDUtils.boomObj,this.x,this.y,null);
                    this.frame.removeList.add(shellObj);
                    this.frame.removeList.add(this);
                    this.frame.Score++;
                }
            }
        }
*        if( this.getRec().intersects(this.frame.planeObj.getRec())){
*            this.frame.removeList.add(this);
*            this.frame.state=2;

*        }
    }
(4)敌方boss 和 我方子弹

我方子弹类中:

public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y-=speed;
*        if( this.frame.bopObj!=null && this.getRec().intersects(this.frame.bopObj.getRec())){
*            this.frame.removeList.add(this);
*            this.frame.isOnBoss++;
*            if(this.frame.isOnBoss % 10 == 0 && this.frame.isOnBoss!=0){//每打10次boss就死亡
*                this.frame.removeList.add(this.frame.bopObj);
*                this.frame.Score+=10;
*                this.frame.bopObj=null;//不变为空就产生不了下一个敌机
*            }
*        }

    }

主类中:

     int boss1or2=1;
*    int isOnBoss=0;
(5) 敌方boss 和 我方飞机

我方飞机类中:

public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
*        if(this.frame.bopObj != null && this.getRec().intersects(this.frame.bopObj.getRec())){
*            this.frame.removeList.add(this.frame.bopObj);
*            this.frame.bopObj=null;
*            this.frame.state=2;
*        }
    }

最终效果:

碰撞效果总演示

(七)加入两种强化果实,分别为增加子弹速度,提高子弹数量。且在敌方boss被击毁时出现

7.1 创建两个强化果实类:

加速子弹类

package com11.fjm0601;

import java.awt.*;

public class SpeedChangeObj extends GameObj{
    public SpeedChangeObj(Image image, int x, int y, int width, int height, double speed, GameWD frame) {
        super(image, x, y, width, height, speed, frame);
        
    }

    @Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;
    }

    @Override
    public Rectangle getRec() {
        return super.getRec();
    }
}

增数子弹类

package com11.fjm0601;

import java.awt.*;

public class AddShellOBj extends GameObj{
    public AddShellOBj(Image image, int x, int y, int width, int height, double speed, GameWD frame) {
        super(image, x, y, width, height, speed, frame);
        
    }

    @Override
    public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;
    }

    @Override
    public Rectangle getRec() {
        return super.getRec();
    }
}

7.2 分别把两种强化果实对应两种敌方boss,当boss坠毁,产生相对应的强化果实

由于把强化果实加入objectList也是对集合的修改操作,所以在主类中添加一个addList,先把果实加入addList,再把addList加入objectList

主类中:

     List<GameObj> removeList=new ArrayList<>();
*    List<GameObj> addList=new ArrayList<>();

......

             objectList.removeAll(removeList);
             removeList.clear();
*            objectList.addAll(addList);
*            addList.clear();

我方子弹类中:

public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y-=speed;
        if( this.frame.bopObj!=null && this.getRec().intersects(this.frame.bopObj.getRec())){
            this.frame.removeList.add(this);
            this.frame.isOnBoss++;
            if(this.frame.isOnBoss % 10 == 0 && this.frame.isOnBoss!=0){
*               Image image=this.frame.bopObj.image;
*               int x=this.frame.bopObj.x;
*               int y=this.frame.bopObj.y;
*               if(image==GameWDUtils.bossPlane1Obj){
*                   speedChangeObj=new SpeedChangeObj(GameWDUtils.SpeedChangeObj,x,y,69,64,4,this.frame);
*                   this.frame.addList.add(speedChangeObj);
*               }else if(image==GameWDUtils.bossPlane2Obj){
*                   addShellOBj=new AddShellOBj(GameWDUtils.AddBulletObj,x,y,66,61,4,this.frame);
*                   this.frame.addList.add(addShellOBj);

*               }
                this.frame.removeList.add(this.frame.bopObj);
                this.frame.Score+=10;
                this.frame.bopObj=null;
            }
        }

    }
7.3 飞机 和 强化果实 的碰撞检测

在两种果实的类中,统一都:

 public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;
*        if(this.getRec().intersects(this.frame.planeObj.getRec())){
*            this.frame.removeList.add(this);
*        }
    }
7.4 碰撞后,在主类中修改 子弹的产生频率 和 子弹的产生位置

(1)子弹的产生频率和flushCount % 10 ==0 这个公式中10所处的位置有关,这个位置的数字越小,子弹产生的频率越高。因此将该处数字替换为n
(2)子弹的产生位置creatShellObj这个方法有关,笔者将子弹横排数量最大值设为3。创建AddShellCount变量,每一次接收果实,都代表AddShellCount这个数+1。

主类中:

BossPLaneObj bopObj=null;
*    int AddShellCount=0;
*    int n=16;

......

*if(state ==1 && planeObj!=null  && flushCount % n==0){//将原先 % 后面的10改为n
                creatShellObj();
            }

......

public void creatShellObj(){
*      if(AddShellCount==0){
*          com11.fjm0601.ShellObj shellObj=new com11.fjm0601.ShellObj(GameWDUtils.SelfBulletObj,planeObj.x+12,planeObj.y-1,10,9,5,this);
*          objectList.add(shellObj);
*      }else if(AddShellCount==1){
*          com11.fjm0601.ShellObj shellObj=new com11.fjm0601.ShellObj(GameWDUtils.SelfBulletObj,planeObj.x+6,planeObj.y-1,10,9,5,this);
*          objectList.add(shellObj);
*          com11.fjm0601.ShellObj shellObj1=new com11.fjm0601.ShellObj(GameWDUtils.SelfBulletObj,planeObj.x+18,planeObj.y-1,10,9,5,this);
*          objectList.add(shellObj1);
*      }else if(AddShellCount==2){
*          com11.fjm0601.ShellObj shellObj=new com11.fjm0601.ShellObj(GameWDUtils.SelfBulletObj,planeObj.x+1,planeObj.y-1,10,9,5,this);
*          objectList.add(shellObj);
*          com11.fjm0601.ShellObj shellObj1=new com11.fjm0601.ShellObj(GameWDUtils.SelfBulletObj,planeObj.x+12,planeObj.y-1,10,9,5,this);
*          objectList.add(shellObj1);
*          com11.fjm0601.ShellObj shellObj2=new com11.fjm0601.ShellObj(GameWDUtils.SelfBulletObj,planeObj.x+23,planeObj.y-1,10,9,5,this);
*          objectList.add(shellObj2);
*      }
    }

SpeedChangeObj类中:

public void paintSelf(Graphics graphics) {


        super.paintSelf(graphics);
        y+=speed;
        if(this.getRec().intersects(this.frame.planeObj.getRec())){
            this.frame.removeList.add(this);
*           if(this.frame.n!=1){
*               this.frame.n/=2;
            }
        }
    }

AddShellObj类中:

public void paintSelf(Graphics graphics) {
        super.paintSelf(graphics);
        y+=speed;
        if(this.getRec().intersects(this.frame.planeObj.getRec())){
            this.frame.removeList.add(this);
*           if(this.frame.AddShellCount<2 ){//最大限度就是2,横排产生的最大子弹量就是2+1
*               this.frame.AddShellCount++;
            }
        }
    }

至此,全游戏代码结束。最终演示效果如下:

飞机大战演示

感想:

1.如果在一个自主项目中,每一步都是通过自己的规划,调整,修改,甚至到最后,能够一点不出错的,一开始就能把自己想加入的功能一遍实现。那么我觉得再次重做是十分无聊的,因为第二 第三遍重写时这部分自己早已烂熟于心了,不可能遇到新的错误了,除非第二遍换个思路。
2.体会到了人月神话这本书里面说过的一句话。写代码的过程中,真正让人感到开心的,是不断的去创造去实现的过程。各位平常如果刷到一些小游戏的广告,就能明白飞机大战这款游戏的上限很高,背景音乐,背景音乐可选,子弹特效,最高纪录 强化效果…等等是具有很强大的挖掘价值的,是可以不断往里面加里料的。每当我想到可以把这些我目前不会加入的功能,通过构思和学习,调整与改错最终实现,我都涌现一股强大的兴奋感。
3.不要看我写的细致,最终掌握思路,自己再写一遍。这个项目真的简单的跟喝水一样。

NewCreationList:

这是我的idea文档,里面有我目前来不及实现的各项功能,各位读者搞定飞机大战后,若还有余暇,可尝试加入这里面的功能。

1.加入分叉子弹的强化果实(四周向外分叉)
2.子弹特效(李信炽阳斩)的旋转特效
3.子弹形状也可以改变: 圆粒子弹型 激光型 飞箭型 且我方飞机的形态随着子弹形状的改变而改变形状
3.每一架未曾击落的飞机/与敌方普通飞机相撞/被子弹子弹击中一次,都减少一滴血,初始一共五滴血,
4.加入自杀式飞机
5.加入背景音乐
6.背景音乐可选
7.实现最高纪录的记载,并显示在右上方
8.可全屏,画面不局限于600,800的布局
9.加入飞机速度类的强化果实,一旦得到该强化果实,背景图移动的速度将会提升
10.两个boss的子弹颜色不一样,造成的效果也不一样(减攻速,有减缓特效)(有持续血量降低,中毒特效)
11.可吸收果实上限不为3,可对creatShellObj算法进行优化,不必产生大量if else 结构,也不必逐个编写产生子弹位置。极致的情况就是全屏子弹
12.加入使子弹产生激光特效的果实

优化:
1.在调用planeObj的paintSelf方法时,不要每次都创建一个新的监听器,这样比较不美观,
2.GameWin:21 22
我认为这里的两个布尔变量的设取可以简化一下,以后可以根据吃到果实的数量一次设出布尔变量,后续通过循环调用

欢迎各位读者评论区留言,有什么疑惑和bug都可以和我交流。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐