飞翔的小鸟1.0 JAVA

《飞扬的小鸟》 开发教程+个人笔记

《飞扬的小鸟》是一款曾经风靡一时的虐心小游戏,在本教程的帮助下,你可以从零开始,一步一步的开发出这款小游戏。在开发之前,请先明确以下内容:

  • 开发语言

    Java,主要应用Java的基本语法,Java面向对象的三大特征,以及Java Swing的常见API。

  • 开发工具

    JDK8,或者更高版本的JDK。

  • 注意事项

    开发前,先下载游戏素材。开发时,会使用到一些参数,如坐标、尺寸、速度等。这些参数的值是作者根据经验得出的结论,你可以直接使用这些参数值,也可以尝试自定义这些参数值。

  • 牛客官方项目源码
    https://git.nowcoder.com/999991353/fly-bird.git

  • 个人项目地址
    https://github.com/KYO9700/bird1.0.git
    https://git.nowcoder.com/236681084/BookManager.git

1 创建游戏面板

1.1 创建项目

创建Java项目(bird),在src路径下新建resources目录,并导入所有素材图片,如下图:

在这里插入图片描述

1.2 绘制面板

创建game包,并在此包下创建游戏面板类BirdGame,在该类中增加如下代码:

package com.KYO9700.game;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

/**
 * 游戏界面
 */

public class BirdGame extends JPanel {

    // 背景图片
    static BufferedImage background;

    /**
     * 初始化游戏
     */
    public BirdGame() throws Exception {
        // 初始化背景图片
        background = ImageIO.read(getClass().getResource("/resources/bg.png"));
    }

    /**
     * 绘制界面
     */
    //重写实现paint方法
    //java绘图时,最常使用到的就是paint(Graphics g){...内容...}方法获取画笔,
    //然后利用JPanel等容器作为画布,在JFrame内呈现出内容
    @Override
    public void paint(Graphics g) {
        // 绘制背景
        g.drawImage(background, 0, 0, null);
    }

    /**
     * 启动方法
     */
    public static void main(String[] args) throws Exception {
        //JFrame()创建无标题的初始不可见框架
        JFrame frame = new JFrame();
        //将子组件添加到框架中,frame.add(child); 子组件将被添加到contentPane
        //将指定的组件附加到此容器的末尾。
        //该方法更改布局相关信息,因此使组件层次结构无效。 如果容器已经被显示,则此后必须验证层次结构,以显示添加的组件。
        BirdGame game = new BirdGame();
        frame.add(game);
        //调整此组件的大小,使其宽度为width ,高度为height 。
        //该方法更改布局相关信息,因此使组件层次结构无效。
        frame.setSize(440, 670);
        //设置组件的位置
        //如果组件是null ,或GraphicsConfiguration与此组件关联是null时,窗口被放置在屏幕的中心。 中心点可以使用GraphicsEnvironment.getCenterPoint方法获得。
        //如果组件不是null ,但它当前没有显示,则该窗口将放置在与此组件GraphicsConfiguration GraphicsConfiguration定义的目标屏幕的中心。
        //如果组件不是null并显示在屏幕上,则窗口的位置使得窗口的中心与组件的中心重合。
        frame.setLocationRelativeTo(null);
        //设置用户在此框架上启动“关闭”时默认执行的操作。 您指定以下选项之一:
        //DO_NOTHING_ON_CLOSE (定义在WindowConstants ):不要做任何事情; 要求程序处理WindowListener对象的windowClosing方法的操作。
        //HIDE_ON_CLOSE (在WindowConstants定义):在调用任何已注册的WindowListener对象后自动隐藏框架。
        //DISPOSE_ON_CLOSE (在WindowConstants定义):在调用任何已注册的WindowListener对象后自动隐藏和处理该框架。
        //EXIT_ON_CLOSE (在JFrame定义):使用System exit方法退出exit程序。 仅在应用程序中使用。
        //该值默认设置为HIDE_ON_CLOSE 。 对此属性的值的更改导致触发属性更改事件,属性名称为“defaultCloseOperation”。
        //注意 :当Java虚拟机(VM)中的最后一个可显示的窗口被丢弃时,VM可能会终止。
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //显示窗口
        frame.setVisible(true);
    }

}

笔记:
1、关于java.swing包:
提供所有的windows桌面应用程序包括的控件。比如:Frame , Dialog, Table, List 等等,就是java的图形界面库。
2、# java.awt和javax.swing的联系和区别:
AWT 是Abstract Window ToolKit (抽象窗口工具包)的缩写,这个工具包提供了一套与本地图形界面进行交互的接口。当我们利用 AWT 来构件图形用户界面的时候,我们实际上是在利用操作系统所提供的图形库。由于不同操作系统的图形库所提供的功能是不一样的,在一个平台上存在的功能在另外一个平台上则可能不存在。
Swing 是在AWT的基础上构建的一套新的图形界面系统,它提供了AWT 所能够提供的所有功能,并且用纯粹的Java代码对AWT 的功能进行了大幅度的扩充。由于 Swing 控件是用100%的Java代码来实现的,因此在一个平台上设计的树形控件可以在其他平台上使用。
3、JPanel类是什么
JPanel 是 Java图形用户界面(GUI)工具包swing中的面板容器类,包含在javax.swing 包中,是一种轻量级容器,可以加入到JFrame窗体中。JPanel默认的布局管理器是FlowLayout,其自身可以嵌套组合,在不同子容器中可包含其他组件(component),如JButton、JTextArea、JTextField 等,功能是对窗体上的这些控件进行组合,相当于C++和C#中的Panel类。
4、关于JFrame容器:
https://blog.csdn.net/liujun13579/article/details/7756729?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-2.control
5、paint(Graphics g){…内容…}方法
java绘图时,最常使用到的就是paint(Graphics g){…内容…}方法获取画笔,
然后利用JPanel等容器作为画布,在JFrame内呈现出内容
https://blog.csdn.net/ye_se_cong_cong/article/details/72729021
https://blog.csdn.net/syhdeclan/article/details/81566789?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
6、JFrame JPanel 与 paint()的调用
https://blog.csdn.net/qq_43026792/article/details/86557391

1.3 测试一下

运行main方法,效果如下图:

在这里插入图片描述

2 设计游戏道具

2.1 设计地面

定义地面类(Ground),代码如下:

package com.KYO9700.game;  
  
import javax.imageio.ImageIO;  
import java.awt.image.BufferedImage;  
  
//2 设计游戏道具  
//2.1 设计地面  
/**  
 * 地面  
  */  
class Ground {  
  
    // 图片  
  BufferedImage image;  
    //地面图片的基本参数:位置、宽高,用于绘图  
  // 位置  
  int x, y;  
    // 宽高  
  int width, height;  
  
    // 初始化地面  
  public Ground() throws Exception {  
        image = ImageIO.read(getClass().getResource("/resources/ground.png"));  
        //宽高通过读取素材确定  
  width = image.getWidth();  
        height = image.getHeight();  
        x = 0;  
        y = 500;  
    }  
  
    // 地面的动画逻辑,左移、复位,由step()函数实现  
  // 向左移动一步  
  public void step() {  
        x--;  
        if (x == -109) {  
            x = 0;  
        }  
    }  
  
}

2.2 设计柱子

定义柱子类(Column),代码如下:

package com.KYO9700.game;  
  
import javax.imageio.ImageIO;  
import java.awt.image.BufferedImage;  
import java.util.Random;  
  
//2 设计游戏道具  
//2.2 设计柱子  
/**  
 * 柱子  
  */  
class Column {  
  
    // 图片  
  BufferedImage image;  
    // 作图的基本参数,位置和宽高  
  // 位置  
  int x, y;  
    // 宽高  
  int width, height;  
    // 柱子之间的缝隙,固定的,与素材图片一致  
  int gap;  
    // 柱子之间的距离,这里设置为固定的  
  int distance;  
    // 随机数工具,用于确定缝隙的高度偏移  
  Random random = new Random();  
  
    /**  
 * 初始化第N个柱子  
  */  
  public Column(int n) throws Exception {  
        image = ImageIO.read(getClass().getResource("/resources/column.png"));  
        //宽高通过读取素材确定  
  width = image.getWidth();  
        height = image.getHeight();  
        gap = 144;  
        distance = 245;  
        x = 550 + (n - 1) * distance;  
        y = random.nextInt(218) + 132;  
    }  
  
    // 柱子地面的动画逻辑,与地面的动画逻辑类似,左移、复位,由step()函数实现  
  // 向左移动一步  
  public void step() {  
        x--;  
        //假如柱子已经完全移出画面,再去到画面前方,并重设高度偏移  
  if (x == -width / 2) {  
            x = distance * 2 - width / 2;  
            y = random.nextInt(218) + 132;  
        }  
    }  
}

2.3 设计小鸟

定义小鸟类(Bird),代码如下:

package com.KYO9700.game;  
  
import javax.imageio.ImageIO;  
import java.awt.image.BufferedImage;  
  
//2 设计游戏道具  
//2.3 设计小鸟  
/**  
 * 小鸟  
  */  
class Bird {  
  
    // 图片  
  BufferedImage image;  
    // 位置  
  int x, y;  
    // 宽高  
  int width, height;  
    // 大小(用于碰撞检测)  
  int size;  
  
    // 重力加速度  
  double g;  
    // 位移的间隔时间  
  double t;  
    // 最初上抛速度  
  double v0;  
    // 当前上抛速度  
  double speed;  
    // 经过时间t之后的位移  
  double s;  
    // 小鸟的倾角(弧度)  
  double alpha;  
  
    // 一组图片,记录小鸟的动画帧  
  BufferedImage[] images;  
    // 动画帧数组的下标  
  int index;  
  
    // 初始化小鸟  
  public Bird() throws Exception {  
        // 初始化基本参数  
  image = ImageIO.read(getClass().getResource("/resources/0.png"));  
        width = image.getWidth();  
        height = image.getHeight();  
        x = 132;  
        y = 280;  
        size = 40;  
  
        // 初始化位移参数  
  g = 4;//重力加速度  
  v0 = 20;  
        t = 0.25;  
        speed = v0;  
        s = 0;  
        alpha = 0;  
  
        // 初始化动画帧参数  
  images = new BufferedImage[8];  
        for (int i = 0; i < 8; i++) {  
            images[i] = ImageIO.read(  
                    getClass().getResource("/resources/" + i + ".png"));  
        }  
        index = 0;  
    }  
  
    // 飞行动作(变化一帧)  
  public void fly() {  
        index++;  
        image = images[(index / 12) % 8];  
    }  
  
    // 移动一步  
  public void step() {  
        double v0 = speed;  
        // 计算上抛运动位移  
  s = v0 * t + g * t * t / 2;  
        // 计算鸟的坐标位置  
  y = y - (int) s;  
        // 计算下次移动速度  
  double v = v0 - g * t;  
        speed = v;  
        // 计算倾角(反正切函数)  
  alpha = Math.atan(s / 8);  
    }  
  
    // 向上飞行  
  public void flappy() {  
        // 重置速度  
  speed = v0;  
    }  
  
    // 检测小鸟是否碰撞到地面  
  public boolean hit(Ground ground) {  
        boolean hit = y + size / 2 > ground.y;  
        if (hit) {  
            y = ground.y - size / 2;  
            alpha = -3.14159265358979323 / 2;  
        }  
        return hit;  
    }  
  
    // 检测小鸟是否撞到柱子  
  public boolean hit(Column column) {  
        // 先检测是否在柱子的范围内  
  if (x > column.x - column.width / 2 - size / 2  
  && x < column.x + column.width / 2 + size / 2) {  
            // 再检测是否在柱子的缝隙中  
  if (y > column.y - column.gap / 2 + size / 2  
  && y < column.y + column.gap / 2 - size / 2) {  
                return false;  
            }  
            return true;  
        }  
        return false;  
    }  
}

3 绘制游戏界面

3.1 定义游戏参数

在BirdGame类中增加成员变量,代码如下:

    // 开始图片
    BufferedImage startImage;
    // 结束图片
    BufferedImage gameOverImage;

    // 地面
    Ground ground;
    // 柱子
    Column column1, column2;
    // 小鸟
    Bird bird;

    // 游戏分数
    int score;

    // 游戏状态
    int state;
    // 状态常量
    public static final int START = 0; //开始
    public static final int RUNNING = 1; //运行
    public static final int GAME_OVER = 2; //结束

3.2 初始化游戏参数

在BirdGame的构造方法里,初始化上述成员变量,代码如下:

/**
 * 初始化游戏
 */
public BirdGame() throws Exception {
    // 初始化背景图片
    background = ImageIO.read(getClass().getResource("/resources/bg.png"));

    // 初始化开始、结束图片
    startImage = ImageIO.read(getClass().getResource("/resources/start.png"));
    gameOverImage = ImageIO.read(getClass().getResource("/resources/gameover.png"));

    // 初始化地面、柱子、小鸟
    ground = new Ground();
    column1 = new Column(1);
    column2 = new Column(2);
    bird = new Bird();

    // 初始化分数
    score = 0;

    // 初始化状态
    state = START;
}

3.3 绘制游戏界面

在paint方法内,绘制地面、柱子、小鸟、分数、开始图片、结束图片,代码如下:

/**  
 * 绘制界面  
  */  
//重写实现paint方法  
//java绘图时,最常使用到的就是paint(Graphics g){...内容...}方法获取画笔,  
//然后利用JPanel等容器作为画布,在JFrame内呈现出内容  
@Override  
public void paint(Graphics g) {  
    // 绘制背景  
  g.drawImage(background, 0, 0, null);  
  
    // 绘制地面  
  g.drawImage(ground.image, ground.x, ground.y, null);  
  
    // 绘制柱子  
  g.drawImage(column1.image, column1.x - column1.width / 2, column1.y  
  - column1.height / 2, null);  
    g.drawImage(column2.image, column2.x - column2.width / 2, column2.y  
  - column2.height / 2, null);  
  
    // 绘制小鸟(旋转坐标系)  
  //java.lang.Object  
 // java.awt.Graphics //  java.awt.Graphics2D //该Graphics2D类扩展了Graphics类,以提供对几何,坐标变换,颜色管理和文本布局的更复杂的控制。 这是在Java(tm)平台上呈现二维形状,文字和图像的基础类。  
  Graphics2D g2 = (Graphics2D) g;  
    //随后的渲染绕旋转中心以指定弧度旋转,画出的小鸟的旋转角度由bird对象的属性alpha确定  
  g2.rotate(-bird.alpha, bird.x, bird.y);  
    g.drawImage(bird.image,  
            bird.x - bird.width / 2, bird.y - bird.height / 2, null);  
    //将之前的渲染旋转角度恢复  
  g2.rotate(bird.alpha, bird.x, bird.y);  
  
    // 绘制分数  
  //java.lang.Object  
 // java.awt.Font //Font类表示字体,用于以可见的方式呈现文本。 一种字体提供映射字符 字形的序列的序列和上呈现字形序列所需的信息Graphics个Component对象。  
  //从指定的名称,样式和点大小创建一个新的 Font  Font f = new Font(Font.SANS_SERIF, Font.BOLD, 40);//BOLD-大胆的风格常数。SANS_SERIF-逻辑字体“SansSerif”的规范系列名称的字符串常量。  
  g.setFont(f);  
    g.drawString("" + score, 40, 60);  
    g.setColor(Color.WHITE);  
    g.drawString("" + score, 40 - 3, 60 - 3);  
  
    // 绘制开始与结束界面  
  switch (state) {  
        case START:  
            g.drawImage(startImage, 0, 0, null);  
            break;  
        case GAME_OVER:  
            g.drawImage(gameOverImage, 0, 0, null);  
            break;  
    }  
}

3.4 测试一下

运行main方法,效果如下图:

在这里插入图片描述

在构造方法内将状态设置为RUNNING,重新执行main方法,效果如下图:

在这里插入图片描述

在构造方法内将状态设置为GAME_OVER,重新执行main方法,效果如下图:

在这里插入图片描述

测试完毕,别忘了将构造方法内的状态重置为START。

4 编排游戏动作

4.1 定义开始方法

在BirdGame类中增加开始游戏的方法,并在此方法内编排游戏的动作,代码如下:

// 开始游戏
public void action() throws Exception {
    // 不断的移动与重绘
    while (true) {
        switch (state) {
            case START:
                // 小鸟做出飞行动作
                bird.fly();
                // 地面向左移动一步
                ground.step();
                break;
            case RUNNING:
                // 地面向左移动一步
                ground.step();
                // 柱子向左移动一步
                column1.step();
                column2.step();
                // 小鸟做出飞行动作
                bird.fly();
                // 小鸟上下移动一步
                bird.step();
                // 计算分数
                if (bird.x == column1.x || bird.x == column2.x) {
                    score++;
                }
                // 检测是否发生碰撞
                if (bird.hit(ground) || bird.hit(column1) || bird.hit(column2)) {
                    state = GAME_OVER;
                }
                break;
        }
        // 重新绘制界面
        // 调用repaint方法重新绘制界面
        //public void repaint(long tm)重写组件。  
        //public void repaint()重新编辑这个组件。
        //如果此组件是轻量级组件,则此方法将尽快调用此组件的paint方法。  
        //否则,该方法会尽快调用此组件的update方法。
        repaint();
        // 休眠 1000/60 毫秒
        Thread.sleep(1000 / 60);
    }
}

4.2 调用开始方法

在main方法中调用上述方法,代码如下:

/**
 * 启动方法
 */
public static void main(String[] args) throws Exception {
    JFrame frame = new JFrame();
    BirdGame game = new BirdGame();
    frame.add(game);
    frame.setSize(440, 670);
    frame.setLocationRelativeTo(null);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
    game.action();
}

4.3 测试一下

运行main方法,效果如下图:

在这里插入图片描述

在构造方法内将状态设置为RUNNING,重新运行main方法,效果如下图:

在这里插入图片描述

测试完毕,别忘了将构造方法内的状态重置为START。

5 定义操控逻辑

5.1 增加监听事件

在action方法中,增加鼠标单击事件的监听器,代码如下(增加在while循环的前面):

    // 鼠标监听器
    // 适配器类已经实现相应接口,MouseAdapter抽象类实现了MouseListener接口,  
    // 因此可以使用MouseAdapter(的子类)创建的对象做监视器,只需重写需要的接口方法即可。
    MouseListener l = new MouseAdapter() {
        // 鼠标按下事件
        public void mousePressed(MouseEvent e) {
            try {
                switch (state) {
                    case START:
                        // 在开始状态,按下鼠标则转为运行状态。
                        state = RUNNING;
                        break;
                    case RUNNING:
                        // 在运行状态,按下鼠标则小鸟向上飞行。
                        bird.flappy();
                        break;
                    case GAME_OVER:
                        // 在结束状态,按下鼠标则重置数据,再次转为开始态。
                        column1 = new Column(1);
                        column2 = new Column(2);
                        bird = new Bird();
                        score = 0;
                        state = START;
                        break;
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    };

    // 将监听器添加到当前的面板上
    addMouseListener(l);

笔记:
adapter意为适配器。

我们知道当一个类实现一个接口时,即使不准备使用某个方法,也必须给出接口中所有方法的实现,适配器可以替代接口来处理事件,当java提供处理事件的接口中多于一个方法时,java就相应的提供一个适配器类,比如:MouseAdapter,windowAdapter等等。

适配器已经实现了相应的接口,例如MouseAdapter抽象类实现了MouseListener接口,因此可以使用MouseAdapter的子类创建的对象做监视器,只需重写需要的接口方法即可。

一个是类,一个是接口。

5.2 测试一下

运行main方法,效果如下图:

在这里插入图片描述

6 生成可执行.exe文件

6.1 使用exe4j生成.exe文件

在IDEA中build项目的jar包,然后使用exe4j生成.exe文件。

6.2 测试一下

运行.exe文件,效果如下图:

在这里插入图片描述

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值