JAVA项目:Java实现飞扬的小鸟(Flappy Bird)

r囧r小猫 2022-12-10 08:54 278阅读 0赞

飞扬的小鸟(Flappy Bird)

需求描述

游戏加载完毕点击界面即可开始游戏。

鼠标点击控制小鸟飞行,或者通过键盘控制小鸟的前后上下也可以,不要撞到管道哦!

控制好小鸟越过障碍飞得更远,获得更高的积分。

使用的技术点

  • 变量
  • 分支语句
  • 循环语句
  • 面向对象
  • 异常处理
  • Random随机数
  • StringBuffer字符串操作
  • IO操作
  • 多线程
  • swing组件
  • 。。。。

需求分析

代码实现

1、实现界面背景

step1:首先新建一个class表示背景类BackGround。我们要在该类中,加载背景图片。

创建一个包pics,里面先存放背景图:bg.png。

先定义一个常量类Constant,专门用于存储程序中的常量。

代码实现:

  1. package com.ruby.demo;
  2. /**
  3. * 常量类
  4. * @author ruby
  5. *
  6. */
  7. public class Constant {
  8. // 图片路径
  9. public static String PATH_PIC = "/pics/";
  10. // 背景图片路径
  11. public static String PATH_BACKGROUND = PATH_PIC + "bg.png";
  12. }

然后创建BackGround类:

代码实现:

  1. package com.ruby.demo;
  2. import java.awt.image.BufferedImage;
  3. import java.io.IOException;
  4. import javax.imageio.ImageIO;
  5. /**
  6. * step1:背景类:单例模式
  7. *
  8. * @author ruby
  9. *
  10. */
  11. public class BackGround {
  12. public BufferedImage img = null;// 背景图片
  13. public int width = 0;// 背景的宽度
  14. public int height = 0;// 背景的高度
  15. private static BackGround instance = null;
  16. private BackGround() {
  17. try {
  18. // ImageIO用于加载图片资源
  19. // this.getClass().getResource根据当前路径加载图片
  20. img = ImageIO.read(this.getClass().getResource(Constant.PATH_BACKGROUND));
  21. // 获取背景图片长度和高度
  22. width = img.getWidth();// 获取图片的宽度
  23. height = img.getHeight();// 获取图片的宽度
  24. System.out.println("widthBg=" + width + ", heightBg=" + height);
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. // 实现线程安全的懒汉式
  30. public static BackGround getInstance() {
  31. if (instance == null) {
  32. instance = new BackGround();
  33. }
  34. return instance;
  35. }
  36. }

代码实现:

  1. package com.ruby.demo;
  2. import java.awt.image.BufferedImage;
  3. import java.io.IOException;
  4. import javax.imageio.ImageIO;
  5. /**
  6. * step1:背景类:单例模式
  7. *
  8. * @author ruby
  9. *
  10. */
  11. public class BackGround {
  12. public BufferedImage img = null;// 背景图片
  13. public int width = 0;// 背景的宽度
  14. public int height = 0;// 背景的高度
  15. private static BackGround instance = null;
  16. private BackGround() {
  17. try {
  18. // ImageIO用于加载图片资源
  19. // this.getClass().getResource根据当前路径加载图片
  20. img = ImageIO.read(this.getClass().getResource(Constant.PATH_BACKGROUND));
  21. // 获取背景图片长度和高度
  22. width = img.getWidth();// 获取图片的宽度
  23. height = img.getHeight();// 获取图片的宽度
  24. System.out.println("widthBg=" + width + ", heightBg=" + height);
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. // 实现线程安全的懒汉式
  30. public static BackGround getInstance() {
  31. if (instance == null) {
  32. instance = new BackGround();
  33. }
  34. return instance;
  35. }
  36. }

说明:

  • 整个项目只有这一个背景,所以可以设计为单例模式。
  • 通过getResource()方法加载图片资源。

step2:然后创建一个面板类,上面用于实现背景,小鸟等。GamePanel

这里主要重写paint()方法,将背景图片,显示到面板上。

代码实现:

  1. package com.ruby.demo;
  2. import java.awt.Graphics;
  3. import java.awt.Image;
  4. import java.awt.event.MouseAdapter;
  5. import java.awt.event.MouseEvent;
  6. import java.io.IOException;
  7. import java.text.SimpleDateFormat;
  8. import java.util.Date;
  9. import javax.imageio.ImageIO;
  10. import javax.swing.JPanel;
  11. /**
  12. * step2:自定义JPanel类的子类
  13. *
  14. * @author ruby
  15. *
  16. */
  17. public class GamePanel extends JPanel {
  18. private BackGround bg = null;// 声明背景对象
  19. /*
  20. * 构造函数
  21. */
  22. public GamePanel() {
  23. // 单例模式声明背景对象和地面对象
  24. bg = BackGround.getInstance();
  25. }
  26. /**
  27. * 当前面板中绘制组件(加载图片等)
  28. *
  29. * paint方法会在初始化以及最小和最大化时自动调用该方法(即窗口发生变化时,jvm都会自动调用该方法用于绘制面板)
  30. */
  31. @Override
  32. public void paint(Graphics g) {
  33. super.paint(g);
  34. System.out.println("paint方法被调用" + getCurrentTime());
  35. // Graphics对象绘制背景图案
  36. g.drawImage(bg.img, 0, 0, null);
  37. }
  38. // 获取当前时间
  39. public String getCurrentTime() {
  40. Date day = new Date();
  41. SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
  42. return df.format(day);
  43. }
  44. }

step3:创建一个窗体类GameFrame,里面添加刚刚创建的GamePanel对象。

但是首先要在Constant常量类中,设置一些常量:

代码:

  1. // 界面参数
  2. public static String GAME_TITLE = "飞翔吧小鸟";
  3. public static int WINDOW_WIDTH = 432;
  4. public static int WINDOW_HEIGHT = 644;

代码实现:

  1. package com.ruby.demo;
  2. import javax.swing.JFrame;
  3. /**
  4. * step3:窗体
  5. * @author ruby
  6. *
  7. */
  8. public class GameFrame extends JFrame {
  9. // 初始化窗体
  10. public void initFrame() {
  11. // 设置窗口标题
  12. setTitle(Constant.GAME_TITLE);
  13. // 设置窗口大小
  14. setSize(Constant.WINDOW_WIDTH, Constant.WINDOW_HEIGHT);
  15. // 添加Panel
  16. GamePanel panel = new GamePanel();
  17. add(panel);
  18. // 设置窗口坐标
  19. setLocationRelativeTo(null);
  20. // 设置窗口可见
  21. setVisible(true);
  22. // 设置窗口大小不可调整
  23. setResizable(false);
  24. // 监听窗口关闭,程序结束
  25. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  26. }
  27. }

step4:创建Main类,表示程序的入口:

代码实现:

  1. package com.ruby.demo;
  2. /**
  3. * 程序的入口
  4. * @author ruby
  5. *
  6. */
  7. public class Main {
  8. public static void main(String[] args) {
  9. GameFrame frame = new GameFrame();
  10. frame.initFrame();
  11. }
  12. }

运行效果:

e4d651e22aca708a8044e16903fc4ef0.png

2、实现地面移动

思路:

首先得先在Constant常量类中,添加地面的图片路径,并且将地面图片拷贝到pics资源目录下。

  1. // 地面图片路径
  2. public static String PATH_GROUND = PATH_PIC + "ground.png";

然后创建Ground类:

然后创建Ground类:

  1. package com.ruby.demo;
  2. import java.awt.image.BufferedImage;
  3. import java.io.IOException;
  4. import javax.imageio.ImageIO;
  5. /**
  6. * step1:地面类
  7. *
  8. * @author ruby
  9. *
  10. */
  11. public class Ground {
  12. public BufferedImage img = null;// 地面图片
  13. public int x, y;// 地面绘制的起始坐标
  14. public int width = 0;// 地面的宽度
  15. public int height = 0;// 地面的高度
  16. private static Ground instance = null;
  17. private Ground() {
  18. try {
  19. // 单例模式
  20. BackGround bg = BackGround.getInstance();
  21. // ImageIO用于加载图片资源
  22. // this.getClass().getResource根据当前路径加载图片
  23. img = ImageIO.read(this.getClass().getResource(Constant.PATH_GROUND));
  24. // 获取地面图片的长度和高度
  25. width = img.getWidth();// 获取图片的宽度
  26. height = img.getHeight();// 获取图片的宽度
  27. x = 0;
  28. y = bg.height - height;// 背景高度与地面图片高度的差值就是地面图片的起始Y坐标
  29. System.out.println("widthGround=" + width + ", heightGround=" + height);
  30. System.out.println("x=" + x + ", y=" + y);
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. // 实现懒汉式
  36. public static Ground getInstance() {
  37. if (instance == null) {
  38. instance = new Ground();
  39. }
  40. return instance;
  41. }
  42. }

说明:在该类中要计算出地面的坐标点x和y。

x为0即可,而y的值为背景图片的高度减去地面图片的高度。

然后添加一个地面移动的方法:

  1. // 地面移动
  2. public void move(BackGround bg) {
  3. x--;
  4. if (x == bg.width +9 - width) {// 9为修正值,根据地面移动效果调整该数值,保证图片移动自然流畅。是地面图片中条纹间距的一半数值。
  5. x = 0;
  6. }
  7. System.out.println("x=" + x);
  8. }

所谓的运动地面,就是就是修改x的值,地面向左侧移动,所以x—。

然后要修改GamePanel中的paint()方法,绘制地面:

  1. @Override
  2. public void paint(Graphics g) {
  3. super.paint(g);
  4. System.out.println("paint方法被调用" + getCurrentTime());
  5. // Graphics对象绘制背景图案
  6. g.drawImage(bg.img, 0, 0, null);
  7. // 绘制地面
  8. g.drawImage(ground.img, ground.x, ground.y, null);
  9. }

然后在GamePanel类中添加一个新增的方法action(),表示游戏的动作,我们需要设置一个死循环,来让地面不停的移动。

多久移动一次呢,我们可以设置一个速度,其实就表示游戏的速度,可以初始化一个常量,每当过一关,游戏的速度可以适当的增加。

现在定义一个常量:

  1. public static int MOVE_SPEED = 40;// 地面及柱子移动初始速度。当积分累加,速度会递增

在GamePanel类中添加一个变量speed,表示速度:

  1. private int speed = 0;// 柱子和地面的移动速度

修改GamePanel()构造方法:

  1. // 初始化速度
  2. speed = Constant.MOVE_SPEED;

然后添加一个action()方法,

  1. public void action() {
  2. // 设置鼠标监听
  3. // 设置键盘监听
  4. // 通过监听鼠标事件,监听到state的变化,无限循环,不断切换状态
  5. while (true) {
  6. // 地面移动
  7. ground.move(bg);
  8. // 线程休眠(因为是无限循环,下一次循环开始需要一段休息时间,这样才能让程序有缓冲的执行时间)
  9. try {
  10. Thread.sleep(1000 / speed);// 调节游戏速度
  11. // 重新绘制(重新调用面板paint方法)
  12. this.repaint();
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

为了不让地面移动太快,频繁的绘制界面,我们需要让程序睡眠一下,这个睡眠的时间,其实表示游戏的速度,就是地面和柱子移动的速度。

然后在GameFrame的initFrame()方法中,调用action()方法:

  1. // 面板运行
  2. panel.action();

运行效果:

1af0e4eb88a1c828720d8bc49fcc6094.png

3、显示开始和结束

思路:整个游戏分为3个状态:游戏开始前,开始玩,游戏结束。定义一个变量state,0表示未开始,1表示玩ing,2表示game over,游戏结束。

当游戏未开始状态,显示开始图片。

点击开始游戏后,可以玩,当小鸟撞到地面或者天空或者柱子,游戏结束。

游戏结束时,显示结束图片。

在GamePanel类中,添加一个变量state

  1. private static int state = 0;// 游戏状态,0表示游戏未开始,1表示游戏正在进行,2表示游戏结束

然后在构造方法中,初始化state的状态,以及加载开始和结束的图片:

  1. /*
  2. * 构造函数
  3. */
  4. public GamePanel() {
  5. // 初始化数据
  6. // 单例模式声明背景对象和地面对象
  7. bg = BackGround.getInstance();
  8. ground = Ground.getInstance();
  9. // state = 0表示游戏未开始
  10. state = 0;
  11. try {
  12. // 加载开始和结束图片
  13. imgStart = ImageIO.read(this.getClass().getResource(Constant.PATH_START));
  14. imgOver = ImageIO.read(this.getClass().getResource(Constant.PATH_GAMEOVER));
  15. } catch (IOException e) {
  16. e.printStackTrace();
  17. }
  18. }

同时将两张图片,添加到pics目录下,并且在Constant类中,添加常量值:

  1. public static String PATH_START = PATH_PIC + "start.png";
  2. public static String PATH_GAMEOVER = PATH_PIC + "gameover.png";

修改绘图的方法paint(),先绘制背景,然后根据状态不同,绘制不同的图案,最后绘制地面:

  1. @Override
  2. public void paint(Graphics g) {
  3. super.paint(g);
  4. System.out.println("paint方法被调用" + getCurrentTime());
  5. // Graphics对象绘制背景图案
  6. g.drawImage(bg.img, 0, 0, null);
  7. // 根据状态,绘制不同的图案
  8. if (state == 0) {//游戏未开始
  9. // 游戏未开始时,绘制开始图案及小鸟
  10. g.drawImage(imgStart, 0, 0, null);
  11. } else if (state == 1) {// 游戏开始后
  12. } else if (state == 2) {// 游戏结束
  13. // 游戏结束时,绘制结束图案
  14. g.drawImage(imgOver, 0, 0, null);
  15. }
  16. // 绘制地面
  17. g.drawImage(ground.img, ground.x, ground.y, null);
  18. }

修改action()方法,添加鼠标事件:

  1. public void action() {
  2. // 设置鼠标监听
  3. this.addMouseListener(new MouseAdapter() {
  4. //点击鼠标后
  5. @Override
  6. public void mouseReleased(MouseEvent e) {
  7. super.mouseReleased(e);
  8. switch (state) {
  9. case 0://游戏未开始
  10. // 切换到状态1时的数据
  11. state = 1;
  12. break;
  13. case 1://开始游戏
  14. state = 2;//...
  15. break;
  16. case 2://游戏结束
  17. //游戏结束后,更改状态为0,可以继续下一次游戏
  18. state = 0;
  19. break;
  20. default:
  21. break;
  22. }
  23. }
  24. });
  25. // 设置键盘监听
  26. //....
  27. }

运行效果:

aa7fd617fa0f8a7a8a47df6aabaf25c0.png

点击一下,开始游戏,再点一下结束游戏,效果如图:

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FpYW5mZW5nX2Rhc2h1anU_size_16_color_FFFFFF_t_70

开始游戏的时候,小鸟是灰色的,我们希望小鸟能一直扇动翅膀。

小鸟翅膀扇动,其实就是小鸟的8张图循环轮播。我们可以通过数组来实现。

现在Constant类中定义小鸟的图片数量,以及小鸟的初始位置:

  1. public static int BIRD_PIC_COUNT = 8;// 小鸟皮肤个数
  2. public static int BIRD_POSITION_X = 190;// 小鸟初始化坐标
  3. public static int BIRD_POSITION_Y = 220;

然后创建一个小鸟类Bird:

  1. package com.ruby.demo;
  2. import java.awt.image.BufferedImage;
  3. import java.io.IOException;
  4. import javax.imageio.ImageIO;
  5. /**
  6. * 小鸟类
  7. *
  8. * @author ruby
  9. *
  10. */
  11. public class Bird {
  12. public BufferedImage img = null;// 小鸟图片
  13. public BufferedImage imgs[] = new BufferedImage[Constant.BIRD_PIC_COUNT];// 数组,存储所有小鸟图案
  14. public static int index = 0;// 当前皮肤的序号
  15. public int x, y;// 初始坐标
  16. public int width = 0;// 小鸟的宽度
  17. public int height = 0;// 小鸟的高度
  18. public Bird() {
  19. try {
  20. for (int i = 0; i < 8; i++) {
  21. imgs[i] = ImageIO.read(getClass().getResource(Constant.PATH_PIC + i + ".png"));
  22. }
  23. img = imgs[0];
  24. // 获取小鸟的宽度和高度
  25. width = img.getWidth();
  26. height = img.getHeight();
  27. // 初始化小鸟的坐标位置
  28. x = Constant.BIRD_POSITION_X;
  29. y = Constant.BIRD_POSITION_Y;
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }

添加一个小鸟扇动翅膀的方法:

  1. // 小鸟飞翔的图片切换
  2. public void fly() {
  3. index++;
  4. // 小鸟图形切换的频率,index/x,x越大,翅膀切换频率越慢
  5. img = imgs[index / 6 % Constant.BIRD_PIC_COUNT];
  6. if (index == 6 * Constant.BIRD_PIC_COUNT) {
  7. index = 0;
  8. }
  9. }

然后在GamePanel类中添加小鸟对象,

  1. private Bird bird = null;// 声明小鸟对象

并在构造方法中初始化:

  1. // 声明小鸟对象
  2. bird = new Bird();

修改paint()方法,在未开始游戏的时候,就要绘制小鸟了:

  1. // 根据状态,绘制不同的图案
  2. if (state == 0) {// 游戏未开始
  3. // 游戏未开始时,绘制开始图案及小鸟
  4. g.drawImage(imgStart, 0, 0, null);
  5. g.drawImage(bird.img, bird.x, bird.y, null);
  6. } else if (state == 1) {// 游戏开始后
  7. } else if (state == 2) {// 游戏结束
  8. // 游戏结束时,绘制结束图案
  9. g.drawImage(imgOver, 0, 0, null);
  10. }

修改action()方法,在循环中,除了移动地面外,还要让小鸟扇动翅膀:

  1. // 通过监听鼠标事件,监听到state的变化,无限循环,不断切换状态
  2. while (true) {
  3. // 地面移动
  4. ground.move(bg);
  5. bird.fly();
  6. // 线程休眠(因为是无限循环,下一次循环开始需要一段休息时间,这样才能让程序有缓冲的执行时间)
  7. try {
  8. Thread.sleep(1000 / speed);// 调节游戏速度
  9. // 重新绘制(重新调用面板paint方法)
  10. this.repaint();
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }

运行结果:

c5b84cc4cb6283a541e18e737ad30f94.png

4、游戏开始

游戏开始后,小鸟就要移动了。还要添加上下两根柱子,柱子向左侧移动,通过点击鼠标,让小鸟上下移动,来躲避柱子。

小鸟上下飞动

我们现在实现小鸟的部分:

游戏开始时,小鸟在距离屏幕左侧120左右的位置就可以:

在Constant类中定义,游戏开始时小鸟的位置:

  1. public static int BIRD_FLY_POSITION_X = 120;// 小鸟开始飞翔时初始坐标

首先修改action()中,case 0里,先修改小鸟的位置

  1. public void action() {
  2. // 设置鼠标监听
  3. this.addMouseListener(new MouseAdapter() {
  4. // 点击鼠标后
  5. @Override
  6. public void mouseReleased(MouseEvent e) {
  7. super.mouseReleased(e);
  8. switch (state) {
  9. case 0:// 游戏未开始
  10. // 切换到状态1时的数据
  11. state = 1;
  12. bird.x = Constant.BIRD_FLY_POSITION_X;// 小鸟飞的初始x坐标
  13. break;
  14. .....
  15. }

然后在paint()方法中,state如果为1,代表游戏开始,应该绘制小鸟和两根柱子,我们先画小鸟:

  1. else if (state == 1) {// 游戏开始后
  2. //绘制小鸟和两根柱子
  3. g.drawImage(bird.img, bird.x, bird.y, null);
  4. }

运行效果:

d4ca8691b678b4949eb9025ac82f26f4.png

小鸟默认会向下掉,要考虑重力加速度。当点击鼠标的时候,会向上移动。

先在Constant提供常量:

  1. public static double GRAVITATIONAL_ACCELERATION = 9.8;
  2. public static double DOWN_TIME = 0.18; // 小鸟自然下降的时长

在小鸟Bird类中,提供一些变量:

  1. public double g = Constant.GRAVITATIONAL_ACCELERATION; // 重力加速度
  2. public double v = 0;// 下降速度
  3. public double t = Constant.DOWN_TIME;// 下降时间
  4. public double h;// 下降的距离

再添加两个方法:down()表示下降

  1. // 小鸟自然下降
  2. public void down() {
  3. v = v - g * t; // 末速度Vt=Vo-gt
  4. h = v * t - g * t * t / 2; // 位移h=Vot-gt²/2
  5. y = y - (int) h;
  6. }

然后修改action()方法:

  1. while (true) {
  2. // 地面移动
  3. ground.move(bg);
  4. bird.fly();
  5. if(state == 0){
  6. }else if(state == 1){
  7. bird.down();//小鸟下降
  8. }else if(state == 2){
  9. }
  10. // 线程休眠(因为是无限循环,下一次循环开始需要一段休息时间,这样才能让程序有缓冲的执行时间)
  11. try {
  12. Thread.sleep(1000 / speed);// 调节游戏速度
  13. // 重新绘制(重新调用面板paint方法)
  14. this.repaint();
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. }
  18. }

运行结果:

029e080d9840f94382bfd106ec532767.png

当点击鼠标时,小鸟会向上飞,每次向上20。先在Constant中添加一个常量:

  1. public static double UP_SPEED = 20;// 上升的速度

在Bird中,再添加一个方法:

  1. // 上升,点鼠标或点键盘向上键
  2. public void up() {
  3. v = Constant.UP_SPEED;
  4. }

修改action(),在鼠标抬起的事件中,如果state为1,那么要调用up()方法,让小鸟上升:

  1. ...
  2. public void mouseReleased(MouseEvent e) {
  3. super.mouseReleased(e);
  4. switch (state) {
  5. case 0:// 游戏未开始
  6. // 切换到状态1时的数据
  7. state = 1;
  8. bird.x = Constant.BIRD_FLY_POSITION_X;// 小鸟飞的初始x坐标
  9. break;
  10. case 1:// 开始游戏
  11. // 当状态1时,小鸟点击向上移动
  12. bird.up();
  13. break;
  14. ...

柱子左右移动

首先在pics下放图片资源pillar.png。然后在Constant类中,添加常量:

  1. // 柱子参数
  2. public static String PATH_PILLAR = PATH_PIC + "pillar.png";
  3. public static int PILLAR_GAP = 144;// 柱子通道距离
  4. public static int PILLAR_DISTANCE = 244;// 柱子间距

然后创建柱子类Pillar类:

  1. package com.ruby.demo;
  2. import java.awt.image.BufferedImage;
  3. import java.io.IOException;
  4. import java.util.Random;
  5. import javax.imageio.ImageIO;
  6. /**
  7. * 柱子类 其中构造方法中需要背景对象和地面对象
  8. *
  9. * @author ruby
  10. *
  11. */
  12. public class Pillar {
  13. public BufferedImage img;// 图片
  14. public int x, y;// 坐标
  15. public int width = 0;// 柱子宽度
  16. public int height = 0;// 柱子高度
  17. Random random = new Random();// 一个生成随机数的对象
  18. private int max, min = 0;// 为了保证柱子通道能够完全显示在屏幕上,所以存在柱子在Y坐标的最大值和最小值
  19. public Pillar(BackGround bg, Ground ground) {
  20. try {
  21. img = ImageIO.read(getClass().getResource(Constant.PATH_PILLAR));
  22. width = img.getWidth();
  23. height = img.getHeight();
  24. System.out.println("柱子width=" + width + ",height=" + height);
  25. x = bg.width;
  26. max = (height - Constant.PILLAR_GAP) / 2;
  27. min = (height - Constant.PILLAR_GAP) / 2 - (bg.height - Constant.PILLAR_GAP - ground.height);
  28. y = -(min + random.nextInt(max - min));
  29. // System.out.println("y=" + y);
  30. } catch (IOException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }

小鸟闯关的柱子,每隔244间距,就要再产生一根柱子。柱子的高度通过随机数来产生,先计算出柱子的高度:柱子图片高度-柱子通道距离114,然后除以2。柱子的最小高度,就是柱子的高度减去背景图高度-地面高度-通道距离。

柱子的y坐标,就因该是柱子的高度和柱子最小高度之间的随机数。

再添加一个移动柱子的方法:

  1. // 柱子移动,游戏一旦开始则柱子移动
  2. public void move(BackGround bg) {
  3. x--;
  4. if (x == -width) {
  5. x = bg.width;
  6. y = -(min + random.nextInt(max - min));
  7. // System.out.println("y=" + y);
  8. }
  9. }

然后修改GamePanel类,创建2个柱子对象,因为游戏界面中,最多出现2根柱子。然后再构造方法中,实例化两个柱子对象,并设置x坐标,柱子是从游戏界面右侧,移入到游戏界面上,所以第一个柱子的x值为游戏界面的宽度,第二个柱子要再加柱子间距。

  1. // 声明两个柱子,并分别设置柱子的起始X坐标
  2. p1 = new Pillar(bg, ground);
  3. p1.x = bg.width;
  4. p2 = new Pillar(bg, ground);
  5. p2.x = bg.width + Constant.PILLAR_DISTANCE;

修改paint()方法,游戏开始后,绘制柱子:

  1. else if (state == 1) {// 游戏开始后
  2. // 绘制小鸟和两根柱子
  3. g.drawImage(bird.img, bird.x, bird.y, null);
  4. g.drawImage(p1.img, p1.x, p1.y, null);
  5. g.drawImage(p2.img, p2.x, p2.y, null);
  6. }

然后再action()的循环里,调用两个柱子的移动方法:

  1. while (true) {
  2. // 地面移动
  3. ground.move(bg);
  4. bird.fly();
  5. if (state == 0) {
  6. } else if (state == 1) {
  7. // 游戏开始。地面移动、柱子移动、小鸟飞并自然下降
  8. bird.down();
  9. p1.move(bg);
  10. p2.move(bg);
  11. } else if (state == 2) {
  12. }
  13. // 线程休眠(因为是无限循环,下一次循环开始需要一段休息时间,这样才能让程序有缓冲的执行时间)
  14. try {
  15. Thread.sleep(1000 / speed);// 调节游戏速度
  16. // 重新绘制(重新调用面板paint方法)
  17. this.repaint();
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. }

运行结果:

5ae5ac4372b01c04cc06cbdc31f4f950.png

5、计算得分

然后在Constant类中,添加和得分相关的常量:

  1. // 得分信息的字体大小及坐标
  2. public static int FONT_SIZE = 20;
  3. public static int SCORE_X = 20;
  4. public static int SCORE_Y = 40;

在GamePanel中定义score,表示分数,然后在paint()方法中,绘制分数:

  1. // 绘制文字
  2. Font font = new Font(Font.SERIF, Font.ITALIC, Constant.FONT_SIZE);//字体,倾斜,大小
  3. g.setFont(font);
  4. g.setColor(Color.white);// 这里font和color导包都导java.awt
  5. g.drawString("得分:" + score, Constant.SCORE_X, Constant.SCORE_Y);

要想统计分数,得先计算小鸟的各种碰撞,首先在小鸟的类中,添加一个是否碰撞地面的方法,其实就是检测小鸟y的值:

  1. // 碰撞检测
  2. // 掉落到地面时
  3. public boolean hitGround(BackGround bg, Ground ground) {
  4. if (y + height >= (bg.height - ground.height)) {
  5. return true;
  6. }
  7. return false;
  8. }

再添加一个检测是否碰撞天空的方法,就是游戏界面的顶部:

  1. // 碰撞到舞台顶部时
  2. public boolean hitSky() {
  3. if (y <= 0) {
  4. return true;
  5. }
  6. return false;
  7. }

再添加一个检测碰撞柱子的方法:

  1. // 碰到柱子时的检测
  2. public boolean hitPillar(Pillar p) {
  3. // x方向小鸟和柱子碰撞的条件
  4. if ((x + width) >= p.x && x <= p.x + p.width) {
  5. if (y <= p.y + (p.height - Constant.PILLAR_GAP) / 2
  6. || y >= p.y + (p.height + Constant.PILLAR_GAP) / 2 - height) {
  7. return true;
  8. }
  9. }
  10. return false;
  11. }

要判断小鸟的横向上,碰撞柱子。

在小鸟类里添加一个方法:

  1. // 碰到柱子时的检测
  2. public boolean hitPillar(Pillar p) {
  3. // x方向小鸟和柱子碰撞的条件
  4. if ((x + width) >= p.x && x <= p.x + p.width) {
  5. if (y <= p.y + (p.height - Constant.PILLAR_GAP) / 2
  6. || y >= p.y + (p.height + Constant.PILLAR_GAP) / 2 - height) {
  7. return true;
  8. }
  9. }
  10. return false;
  11. }

再添加一个积分的方法:

  1. // 增加积分,通过柱子通道后调用该方法
  2. public boolean addScore(Pillar p) {
  3. // System.out.println("x=" + x + ",p.x=" + p.x);
  4. if (x == p.x + p.width) {
  5. return true;
  6. }
  7. return false;
  8. }

然后在GamePanel类中修改action()方法:

  1. while (true) {
  2. if (state == 0) {
  3. // 游戏未开始。地面移动,小鸟展翅
  4. ground.move(bg);
  5. bird.fly();
  6. } else if (state == 1) {
  7. // 游戏开始。地面移动、柱子移动、小鸟飞并自然下降
  8. ground.move(bg);
  9. p1.move(bg);
  10. p2.move(bg);
  11. bird.fly();
  12. bird.down();
  13. // 碰到地面、天空、柱子都显示游戏结束。
  14. if (bird.hitGround(bg, ground) || bird.hitSky() || bird.hitPillar(p1) || bird.hitPillar(p2)) {
  15. state = 2;
  16. } else {
  17. // 小鸟每通过一个竹子通道,累计积分,并提高柱子和地面移动速度。
  18. if (bird.addScore(p1) || bird.addScore(p2)) {
  19. score++;
  20. // 每通过一个柱子,速度会递增
  21. speed += 2;
  22. // System.out.println("speed=" + speed);
  23. }
  24. }
  25. }

然后修改鼠标事件的监听:

  1. public void mousePressed(MouseEvent e) {
  2. // TODO Auto-generated method stub
  3. super.mousePressed(e);
  4. switch (state) {
  5. case 0:
  6. // 切换到状态1时的数据
  7. state = 1;
  8. bird.x = Constant.BIRD_FLY_POSITION_X;// 小鸟飞的初始x坐标
  9. musicThread = new MusicThread();
  10. musicThread.start();
  11. break;
  12. case 1:
  13. // 当状态1时,小鸟点击向上移动
  14. bird.up();
  15. break;
  16. case 2:
  17. // 切换到状态0时的数据
  18. musicThread.stopBGM();
  19. state = 0;
  20. score = 0;
  21. // 重置小鸟的位置
  22. bird.x = Constant.BIRD_POSITION_X;
  23. bird.y = Constant.BIRD_POSITION_Y;
  24. bird.v = 0;
  25. // 重置柱子的坐标
  26. p1.x = bg.width;
  27. p2.x = bg.width + Constant.PILLAR_DISTANCE;
  28. // System.out.println("p1==" + p1 + ", p2==" + p2);
  29. break;
  30. default:
  31. break;
  32. }
  33. }

当游戏结束的时候,要初始化小鸟和柱子的数据。

6、添加小鸟的键盘事件

在Constant中添加常量:

  1. public static int DISTANCE_PER_PRESS = 10;// 每点一次鼠标或键盘,移动的位置

然后在小鸟类中,添加键盘的上下左右事件方法:

  1. // 后退,点键盘向左键
  2. public void backward() {
  3. x -= Constant.DISTANCE_PER_PRESS;
  4. }
  5. // 前进,点键盘向右键
  6. public void foward() {
  7. x += Constant.DISTANCE_PER_PRESS;
  8. }
  9. // 点击键盘下降,点键盘向下键
  10. public void pressdown() {
  11. y += Constant.DISTANCE_PER_PRESS;
  12. }

然后在action()中,添加鼠标事件监听:

  1. // 设置键盘监听事件
  2. this.addKeyListener(new KeyAdapter() {
  3. @Override
  4. public void keyPressed(KeyEvent e) {
  5. super.keyPressed(e);
  6. switch (e.getKeyCode()) {
  7. case KeyEvent.VK_UP:
  8. bird.up();
  9. break;
  10. case KeyEvent.VK_RIGHT:
  11. bird.foward();
  12. break;
  13. case KeyEvent.VK_LEFT:
  14. bird.backward();
  15. break;
  16. case KeyEvent.VK_DOWN:
  17. bird.pressdown();
  18. break;
  19. }
  20. }
  21. });

注意,最后要在GameFrame中,可以响应键盘事件:

  1. // 让该Frame中的panel聚焦,可以响应键盘事件
  2. panel.requestFocus();

7、添加背景音乐

先准备一首背景音乐,然后在src上创建一个音乐的资源目录music,并将音乐文件拷贝进去:

然后倒入音频播放的jar包:

在Constant中添加常量:

  1. //音乐路径
  2. public static String PATH_MUSIC = "/music/";
  3. public static String PATH_BGM = PATH_MUSIC + "Ari_Pulkkinen-Funky_Theme.mp3";//music/Ari Pulkkinen-Funky Theme.mp3

然后创建一个线程类,播放音乐,再提供一个停止播放的方法:

  1. package com.ruby.demo;
  2. import java.io.InputStream;
  3. import javazoom.jl.decoder.JavaLayerException;
  4. import javazoom.jl.player.Player;
  5. // 播放音乐
  6. class MusicThread extends Thread {
  7. Player player = null;
  8. @Override
  9. public void run() {
  10. // 继承线程类后,重写run方法,播放音乐的代码
  11. // 1.加载音频文件得到输入流
  12. InputStream inputStream = this.getClass().getResourceAsStream(Constant.PATH_BGM);
  13. try {
  14. // 2.创建Player对象,播放音乐
  15. player = new Player(inputStream);
  16. player.play();
  17. } catch (JavaLayerException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. public void stopBGM(){
  22. if(player != null){
  23. player.close();
  24. }
  25. }
  26. }

然后在GamePanel里创建MusicThread对象,用于播放和停止音乐,在action()中修改代码:

  1. this.addMouseListener(new MouseAdapter() {
  2. // 点击鼠标后
  3. @Override
  4. public void mouseReleased(MouseEvent e) {
  5. super.mouseReleased(e);
  6. switch (state) {
  7. case 0:// 游戏未开始
  8. // 切换到状态1时的数据
  9. state = 1;
  10. bird.x = Constant.BIRD_FLY_POSITION_X;// 小鸟飞的初始x坐标
  11. musicThread = new MusicThread();
  12. musicThread.start();
  13. break;
  14. case 1:// 开始游戏
  15. // 当状态1时,小鸟点击向上移动
  16. bird.up();
  17. break;
  18. case 2:// 游戏结束
  19. // 游戏结束后,更改状态为0,可以继续下一次游戏
  20. musicThread.stopBGM();
  21. state = 0;
  22. score = 0;
  23. // 重置小鸟的位置
  24. bird.x = Constant.BIRD_POSITION_X;
  25. bird.y = Constant.BIRD_POSITION_Y;
  26. bird.v = 0;
  27. // 重置柱子的坐标
  28. p1.x = bg.width;
  29. p2.x = bg.width + Constant.PILLAR_DISTANCE;
  30. break;
  31. default:
  32. break;
  33. }
  34. }
  35. });

在state为0时,开始播放音乐,当时游戏结束state为2时,停止音乐。

发表评论

表情:
评论列表 (有 0 条评论,278人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java飞扬小鸟游戏

    游戏背景 这款游戏的起源是越南独立开发者开发的手机游戏,短时间竟占领了全球各大AppStore免费排行榜首位。游戏中,玩家控制一只小鸟飞过一个个柱子的间隙。飞得越远分数越

    相关 用Unity开发Flappy Bird

    之前上过unity的选修课,学了点java。因为过一阵开发VR还是要unity基础的,于是~ 上网找了教程,找了素材。Siki的教程。 现总结下(我是菜鸟,错误的地方望大

    相关 luogu1941 飞扬小鸟

    题目 题目描述 Flappy Bird 是一款风靡一时的休闲手机游戏。玩家需要不断控制点击手机屏幕的频率来调节小鸟的飞行高度,让小鸟顺利通过画面右方的管道缝隙。如果