用Java编写飞机大战小游戏

项目简介

此项目是bilibili软帝学院的教学项目,由纯Java编写。本博客仅为记录自己遇到的一些错误,同时尽力帮助同时在做类似游戏的同学。
原项目链接:Java小游戏制作:三国战记\捕鱼达人\飞机大战\飞扬小鸟

小知识点

窗体、面板方面

  1. 创建窗体
    • 设置标题
      setTitle(“标题”);
    • 设置窗体大小
      setSize(宽,高);
    • 设置不允许玩家改变窗体大小
      setResizable(false);
    • 设置窗口关闭时自动停止程序
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  2. 创建面板
    • 设置面板的可见性
      frame.setVisible(true);
  3. 将面板加入到窗体中
    frame.add(panel);

线程方面

创建并启动一个线程,控制游戏场景中活动的物体
固定格式

new Thread(){
	public void run(){
		线程需要做的事
		}
	}.start();

键鼠监听器

鼠标监听器

     鼠标移动事件mouseMoved(MouseEvent e){
           相对应函数
         }
        其他监听事件的格式类似
        鼠标单击事件mouseClicked()
        鼠标按下去的事件mousePressed()
        鼠标移入游戏界面的事件mouseEntered()
        鼠标移出游戏界面的事件mouseExited()
MouseAdapter adapter=new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {
				相应的方法
            }
        };
        //将适配器加入到监听器中
        addMouseListener(adapter);
        addMouseMotionListener(adapter);

键盘监听器

可用getKeyCode()函数获得按键的编号

 KeyAdapter kd=new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
					相应的方法
            }
        };
        //同样,需要将适配器加入到监听器中
        frame.addKeyListener(kd);

绘图

固定开头
@Override
public void paint(Graphics g) {
super.paint(g);
用画笔画图
g.drawImage(图片,位置(横坐标),位置(纵坐标),null)

设置字体颜色、粗体、大小等
g.setColor(Color.WHITE);
g.setFont(new Font(“楷体”,Font.BOLD,20));

用画笔写字
g.drawString(“字符串”,位置(横坐标),位置(纵坐标));

遇到的问题

java.lang.IllegalArgumentException: input == null!

如图:java.lang.IllegalArgumentException: input == null!

bug原因:

  1. 路径错误
  2. idea的out文件不同步,out中并没有img文件夹,如图
    用Java编写飞机大战小游戏_第1张图片

解决方法:

原因一:
1. 查看控制台错误提示信息
2. 检查App类中26行附近语句的路径是否正确
原因二:
1. 选中src文件夹后,点击顶部横栏中的build
2. 点击ReBuild ‘img’
3. 点击左上角File,点击Close Project。
4. 再次打开项目即可。
如果还不行,就重启电脑。
本人两种方法都试过,都可以。

源码

  1. GameFrame.class
package ui;

import javax.swing.*;

/**
 * Created by IntelliJ IDEA.
 * User: Zengc
 * Date: 2020/11/12
 * Time: 18:23
 **/
public class GameFrame extends JFrame{
    public GameFrame(){ 
        //设置窗体标题
        setTitle("飞机大战");
        //设置窗体大小
        setSize(512,768);
        //设置位置居中(null表示相对左上角居中)
        setLocationRelativeTo(null);
        //设置不允许玩家改变窗体大小
        setResizable(false);
        //设置窗口关闭时自动停止程序
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    //主函数
    public static void main(String[] args){
        //创建窗体
        GameFrame frame=new GameFrame();

        //创建面板
        GamePanel panel=new GamePanel(frame);

        //将面板加入到窗体中
        frame.add(panel);

        panel.action();

        //设置面板的可见性
        frame.setVisible(true);
    }
}

  1. GamePanel.java
package ui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by IntelliJ IDEA.
 * User: Zengc
 * Date: 2020/11/12
 * Time: 18:44
 **/
//面板类
public class GamePanel extends JPanel {
    BufferedImage bg;                        //背景
    Hero hero=new Hero();
    /**
     *  使用集合代表 敌机大本营
     *  不用数组,是因为不能提前知道大本营中敌机的数量
     *  若用数组,还需要扩容
     * 创建的敌机都会放到这个集合中
     * 绘图时遍历此集合即可
     */
    List eps=new ArrayList();
    /**
     * 英雄机的弹药库
     * 与敌机大本营类似
     */
    ArrayList fs=new ArrayList();
    int  score;                             //得分
    int power=1;                            //火力
    boolean gameover=false;                 //游戏开关

    //创建面板
    public GamePanel(GameFrame frame){

        //设置背景颜色
        setBackground(Color.black);

        //App是工具类,专门用来获取图片的,参数是相对路径
        bg=App.getImg("/img/bg1.jpg");

        /**
         * 鼠标监听事件
         *      鼠标移动事件mouseMoved(MouseEvent e){
         *         相对应函数
         *      }
         *      其他监听事件的格式类似
         *          鼠标单击事件mouseClicked()
         *          鼠标按下去的事件mousePressed()
         *          鼠标移入游戏界面的事件mouseEntered()
         *          鼠标移出游戏界面的事件mouseExited()
         */
        MouseAdapter adapter=new MouseAdapter() {
            @Override
            public void mouseMoved(MouseEvent e) {

                //获取鼠标的位置
                int mx=e.getX();
                int my=e.getY();

                //游戏未结束时,调用函数,将英雄机移动到鼠标位置
                if(!gameover)
                    hero.moveToMouse(mx,my);

                //重画,刷新一下
                repaint();
            }
            @Override
            public void mouseClicked(MouseEvent e) {

                /**
                 * 游戏结束时,鼠标点击画面,重新开始新游戏
                 * 恢复hp
                 * 游戏结束标志置假
                 * 得分清零
                 * 敌机大本营清零
                 * 弹药库清零
                 * 刷新,重画
                 */
                if(gameover){
                    hero = new Hero();
                    gameover=false;
                    score=0;
                    eps.clear();
                    fs.clear();
                    power=1;
                    repaint();
                }
            }
        };

        //将适配器加入到监听器中
        addMouseListener(adapter);
        addMouseMotionListener(adapter);

        //键盘监听事件
        KeyAdapter kd=new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {

                /**
                 * 游戏未结束时,才可以调用相应函数移动
                 * getKeyCode()函数获得按键的编号
                 * KeyEvent.VK_UP、KeyEvent.VK_DOWN、KeyEvent.VK_LEFT、KeyEvent.VK_RIGHT
                 * 分别表示上下左右键的编号
                 * 移动之后需要重画,刷新页面
                 */
                if(!gameover){
                    if(e.getKeyCode()==KeyEvent.VK_UP){
                        hero.moveUp();
                    }else if(e.getKeyCode()==KeyEvent.VK_DOWN){
                        hero.moveDown();
                    }else if(e.getKeyCode()==KeyEvent.VK_LEFT){
                        hero.moveLeft();
                    }else if(e.getKeyCode()==KeyEvent.VK_RIGHT){
                        hero.moveRight();
                    }
                }
                repaint();
            }
        };

        //同样,需要将适配器加入到监听器中
        frame.addKeyListener(kd);
    }

    public void action(){
        /**
         * 创建并启动一个线程,控制游戏场景中活动的物体
         * 固定格式
         * new Thread(){public void run(){..线程需要做的事..}}.start();
         */
        new Thread(){
            public void run(){
                while(true){
                    if(!gameover){
                        epEnter();      //敌机入场
                        epMove();       //敌机向下移动
                        shoot();        //英雄机发射子弹
                        firemove();     //子弹移动
                        shootEp();      //
                        hit();
                        repaint();      //刷新
                    }
                    try {
                        Thread.sleep(10);           //暂停10ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    //英雄机被敌机击中
    private void hit() {
        /**
         * 遍历敌机大本营
         * 若敌机与英雄机相撞
         * 则将相撞的英雄机删除,
         *       英雄机hp-1
         *       得分-10(最低为0)
         *       火力回归最初水平
         *       若血量为0,则游戏结束
         */
        for (int i = 0; i < eps.size(); i++) {
            Ep ep = eps.get(i);
            if(ep.hitBy(hero)){
                eps.remove(ep);
                hero.hp--;
                score-=10;
                if(score<0)
                    score=0;
                power=1;
                if(hero.hp<=0)
                    gameover=true;
            }
        }
    }

    //遍历敌机大本营
    private void shootEp() {
        for (int i = 0; i < fs.size(); i++) {
            Fire fire=fs.get(i);
            bang(fire);
        }
    }

    //敌机被击中后
    private void bang(Fire fire){
        for (int i = 0; i < eps.size(); i++) {
            Ep ep=eps.get(i);

            //如果击中目标
            if(ep.shootBy(fire)){

                //敌机血量下降
                ep.hp--;

                //如果敌机的血量为零,则将此敌机从大本营删除,并加分
                if(ep.hp==0){

                    //如果被击毁的是14号敌机,那么英雄机可以加血或者升级武器
                    if(ep.type==14){
                        if(hero.hp<=5)
                            hero.hp++;
                        else if(power<=3)
                            power++;
                    }

                    eps.remove(ep);
                    score+=10;
                }
                //将发生碰撞的子弹从子弹库中删去
                fs.remove(fire);
            }
        }
    }

    //用于控制子弹发射频率
    int fireindex;
    //英雄机发射子弹
    private void shoot() {

        //GamePanel类action方法中的while循环10次,发射一次子弹
        if(fireindex%5==0){

            //火力为1,英雄机正中间发射子弹
            if(power==1) {
                Fire fire2 = new Fire(hero.x + 33, hero.y - 20);       //中间弹道子弹的起始位置应在靠前方一点
                fs.add(fire2);
            }else if(power==2){

             //火力为2,英雄机两侧发射子弹
                Fire fire1=new Fire(hero.x+11,hero.y);
                fs.add(fire1);
                Fire fire3=new Fire(hero.x+58,hero.y);
                fs.add(fire3);
            }else{

            //火力为3,三弹道一起发射子弹
                Fire fire1=new Fire(hero.x+11,hero.y);
                fs.add(fire1);
                Fire fire2=new Fire(hero.x+33,hero.y-20);
                fs.add(fire2);
                Fire fire3=new Fire(hero.x+58,hero.y);
                fs.add(fire3);
            }
        }
        fireindex++;
    }

    //子弹移动
    private void firemove(){

        //获取子弹库中每发子弹,并调用move函数,进行向下移动
        for (int i = 0; i < fs.size(); i++) {
            Fire fire=fs.get(i);
            fire.move();
        }
    }

    //飞机移动(与子弹移动类似)
    private void epMove() {
        for (int i = 0; i < eps.size(); i++) {
            Ep ep=eps.get(i);
            ep.move();
        }
    }

    int index;      //用于控制敌机进场频率(与子弹发射频率类似)

    //飞机进场
    private void epEnter(){
        if(index%20==0){
            Ep ep=new Ep();
            eps.add(ep);
        }
        index++;
    }

    /**
     * 绘图
     *  画笔Graphics
     *      super.paint(g);
     */
    @Override
    public void paint(Graphics g) {
        //Graphics g 画笔
        super.paint(g);

        //用画笔画图g.drawImage(图片,位置(横坐标),位置(纵坐标),null)

        //画背景及英雄机
        g.drawImage(bg,0,0,null);
        g.drawImage(hero.img,hero.x,hero.y,hero.h,hero.w,null);

        //设置字体颜色、大小等
        g.setColor(Color.WHITE);
        g.setFont(new Font("楷体",Font.BOLD,20));

        //用画笔写字
        g.drawString("分数:"+score,10,30);
        g.drawString("血量:",240,30);

        //绘画英雄机
        for(int i = 0; i < hero.hp; i++) {
            g.drawImage(hero.img,300+35*i,5,30,30,null);
        }

        //绘画敌机
        for(int i=0;i
  1. App.java
package ui;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;

/**
 * Created by IntelliJ IDEA.
 * User: Zengc
 * Date: 2020/11/12
 * Time: 18:56
 **/
public class App {
    /**
     * static 方法 特点
     * 所有对象都共用
     * 并且,不需要创建对象就可以直接调用
     */
    public static BufferedImage getImg(String path){
        try{
            /**
             * Java的IO流(输入输出流)
             * App.class获取App类的路径
             * getResource()方法获取资源
             */
            BufferedImage img = ImageIO.read(App.class.getResource(path));
            return img;
        } catch (IOException e){
            //找不到图片则输出将异常情况输出
            e.printStackTrace();
        }
        return null;
    }
}

  1. Hero.java
package ui;

import java.awt.image.BufferedImage;
import java.io.File;
import java.security.PublicKey;
import java.util.ArrayList;

/**
 * Created by IntelliJ IDEA.
 * User: Zengc
 * Date: 2020/11/12
 * Time: 20:23
 **/
public class Hero extends FlyObject{
    int hp;                       //英雄机的血量
    public Hero(){
        img=App.getImg("/img/hero.png");
        //英雄机的坐标(x,y),此处是英雄机图片的左上角的坐标
        x=200;
        y=500;
        hp=5;
        //确定所绘制的英雄机的宽、高(此处分别等于图片的宽和高)
        w=img.getWidth();
        h=img.getHeight();
    }
    // 让英雄机移动到“鼠标位置“
    public void moveToMouse(int mx,int my){
        x=mx-w/2;               //让英雄机的中心点(而不是左上角)位于鼠标位置
        y=my-w/2;
    }

    /**
     * 键盘控制时所调用的函数
     * 其中的if条件判断是为了确保英雄机始终处于屏幕内
     */
    public void moveUp(){
        y-=20;
        if(y<0)
            y=0;
    }

    public void moveDown(){
        y+=20;
        if(y>768)
            y=768;
    }

    public void moveLeft(){
        x-=20;
        if(x<0)
            x=0;
    }

    public void moveRight(){
        x+=20;
        if(x>512)
            x=512;
    }
}

  1. Ep.java
package ui;

import java.util.Random;

/**
 * Created by IntelliJ IDEA.
 * User: Zengc
 * Date: 2020/11/12
 * Time: 21:54
 **/
public class Ep extends FlyObject{
    int speed;      //敌机的移动速度
    int hp=6;       //敌机的血量
    int type;       //敌机的类型(用于确定Boss机,奖品等)
    public Ep(){

        //获取随机数
        Random rd=new Random();

        //将随机数取整(rd.nextInt()范围是[0,15)),所以index范围是[1,16)即[1,15]
        int index=rd.nextInt(15)+1;

        //确定路径,如果index在1~9,则在index前加0,否则不加
        String path="/img/ep"+(index<10?"0":"")+index+".png";

        //通过工具类App 获得图片
        img=App.getImg(path);

        //绘制大小和图片的大小一致
        w=img.getWidth();
        h=img.getHeight();

        //位置横坐标范围为[0,512-w)
        //纵坐标为-h ,让其开始位置在屏幕外
        x=rd.nextInt(512-w);
        y=-h;

        /**
         * 图片的编号与大小正相关
         * 此处想让大飞机速度慢
         *         小飞机速度块
         * 因此,让敌机速度与编号成反相关
         */
        speed=17-index;

        //记录飞机型号
        type=index;
    }

    //敌飞向下移动
    public void move() {
        y+=speed;
    }

    //判断敌机是否被击中
    public boolean shootBy(Fire fire) {
        //画图即可判断清楚条件
        boolean shoot = x<=fire.x+fire.w &&
                     x>=fire.x-w       &&
                     y<=fire.y+fire.h  &&
                     y>=fire.y-h;
        return  shoot;
    }

    public boolean hitBy(Hero hero) {
        //与ShootBy(Fire fire)类似
        boolean hit = x<=hero.x+hero.w &&
                x>=hero.x-w &&
                y<=hero.y+hero.h &&
                y>=hero.y-h;
        return hit;
    }
}

  1. FlyObject.java
package ui;

import java.awt.image.BufferedImage;

/**
 * Created by IntelliJ IDEA.
 * User: Zengc
 * Date: 2020/11/12
 * Time: 21:59
 **/
public class FlyObject {
    BufferedImage img;
    //定义飞行物的横坐标
    int x;
    //定义飞行物的纵坐标
    int y;
    //定义飞行物的宽
    int w;
    //定义飞行物的高
    int h;
}

  1. Fire.java
package ui;

/**
 * Created by IntelliJ IDEA.
 * User: Zengc
 * Date: 2020/11/13
 * Time: 11:25
 **/
public class Fire extends FlyObject{

    //英雄机的横纵坐标(hx,hy)
    public Fire(int hx,int hy){

        //通过工具类App 获得子弹的图片
        img=App.getImg("/img/fire.png");

        //绘制的大小为图片大小的1/4
        w=img.getWidth()/4;
        h=img.getHeight()/4;

        //子弹的起始位置在英雄机的位置
        x=hx;
        y=hy;
    }

    public void move() {
        y-=10;
    }
}

你可能感兴趣的:(java,游戏)