引言
Flappy Bird 是一款广为人知的经典小游戏,以其简单的操作方式和高难度挑战吸引了全球数百万玩家。游戏的核心机制非常直接——玩家通过点击屏幕使小鸟飞翔,避免碰撞到上下移动的管道,同时尽可能地飞行得更远。这种看似简单的游戏设计隐藏了深层的挑战性和上瘾性,让人不禁一试再试。
尽管 Flappy Bird 的玩法简单,但其背后的代码实现和游戏设计思想却值得深究。对于游戏开发初学者和爱好者来说,复现 Flappy Bird 不仅是一种技术上的练习,也是理解游戏设计精髓的一种方式。本文将探讨如何在 Java 中实现 Flappy Bird 的基本功能,并进一步对游戏进行改进,增加新的特性来提升游戏的趣味性和挑战性。
原始代码概览
点击查看代码
package org.wf.game.flappybird;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
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.io.File;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class FalappyBirdGame {
public static void main(String[] args) {
// 定义画框
JFrame jf = new JFrame("bird_game");
jf.setSize(432, 674);
jf.setAlwaysOnTop(false);
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setResizable(false);
Sky sky = new Sky();
jf.add(sky);
// 显示画框
jf.setVisible(true);
sky.action();
}
}
// 天空类
class Sky extends JPanel {
private static final long serialVersionUID = 1L;
BufferedImage bgBufferImage; // 背景图片
Ground ground = new Ground(); // 地面
Column column = new Column(350); // 钢管
Column column2 = new Column(600); // 钢管
static Bird bird = new Bird(); // 小鸟
int score = 0; // 游戏得分
BufferedImage startBufferImage; // 开始准备界面
boolean isStrat; // 是否开始游戏
BufferedImage overBufferImage; // 游戏结束界面
boolean isOver; // 游戏是否结束
public Sky() {
super();
// 读取图片
File bgImage = new File("images/bg.png");
File starImage = new File("images/start.png");
File overImage = new File("images/gameover.png");
try {
bgBufferImage = ImageIO.read(bgImage);
startBufferImage = ImageIO.read(starImage);
overBufferImage = ImageIO.read(overImage);
} catch (IOException e) {
e.printStackTrace();
}
}
// 绘制界面方法
@Override
public void paint(Graphics graphics) {
// 画背景
graphics.drawImage(bgBufferImage, 0, 0, null);
// 获取新的画笔对象
Graphics2D gg = (Graphics2D) graphics;
gg.rotate(-bird.ratation, bird.bird_x, bird.bird_y);
// 画小鸟
graphics.drawImage(bird.biBufferImage, bird.bird_x - bird.bird_width
/ 2, bird.bird_y - bird.bird_height / 2, null);
gg.rotate(bird.ratation, bird.bird_x, bird.bird_y);
// 画钢管
graphics.drawImage(column.coBufferImage, column.column_x - column.width
/ 2, column.column_y - column.height / 2, null);
graphics.drawImage(column2.coBufferImage, column2.column_x
- column2.width / 2, column2.column_y - column2.height / 2,
null);
// 画地面
graphics.drawImage(ground.grBufferImage, ground.ground_x,
ground.ground_y, null);
// 画文字
graphics.setColor(Color.BLUE);
graphics.setFont(new Font("楷体", Font.ITALIC, 30));
graphics.drawString("分数:" + score, 100, 600);
// 画开始准备图片
if (!isStrat && !isOver) {
graphics.drawImage(startBufferImage, 0, 0, null);
}
// 画结束界面
if (isOver) {
graphics.drawImage(overBufferImage, 0, 0, null);
}
}
// 游戏启动逻辑
public void action() {
// 添加鼠标监听器
MouseAdapter adapter = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
// System.out.println("点击了鼠标");
/*
* 若游戏结束重新开始游戏,游戏恢复初始状态
* 若未结束:鸟飞起来
*/
if (isOver) {
bird = new Bird();
ground = new Ground();
column = new Column(350);
column2 = new Column(600);
score = 0;
isOver = false;
isStrat = false;
} else {
bird.refly();
isStrat = true;
}
}
};
this.addMouseListener(adapter);
// 添加键盘监听器
KeyAdapter keyAdapter = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
char charA = e.getKeyChar();
if (charA == 'w') {
if (bird.bird_y > 20) {
bird.bird_y -= 20;
}
} else if (charA == 's') {
if (bird.bird_y < 465) {
bird.bird_y += 20;
}
} else if (charA == 'a') {
if (bird.bird_x > 20) {
bird.bird_x -= 20;
}
} else if (charA == 'd') {
if (bird.bird_x < 395) {
bird.bird_x += 20;
}
} else if (charA == ' ') {
/*
* 若游戏结束重新开始游戏,游戏恢复初始状态
* 若未结束:鸟飞起来
*/
if (isOver) {
bird = new Bird();
ground = new Ground();
column = new Column(350);
column2 = new Column(600);
score = 0;
isOver = false;
isStrat = false;
} else {
bird.refly();
isStrat = true;
}
}
super.keyPressed(e);
}
};
this.addKeyListener(keyAdapter);
this.requestFocus();
while (true) {
// 判断游戏是否开始
if (isStrat && !isOver) {
ground.move();
column.move();
column2.move();
bird.change();
bird.move_go();
}
// 判断撞击障碍
if (bird.bird_x - bird.bird_width / 2 == column.column_x
+ column.width / 2
|| bird.bird_x - bird.bird_width / 2 == column2.column_x
+ column2.width / 2) {
score++;
}
if (bird.hit(ground) || bird.hit(column) || bird.hit(column2)) {
isStrat = false;
isOver = true;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
repaint();
}
}
}
// 地面类
class Ground {
int ground_x, ground_y; // 地面的坐标
BufferedImage grBufferImage; // 地面图片
public Ground() {
super();
File grImage = new File("images/ground.png");
try {
grBufferImage = ImageIO.read(grImage);
} catch (IOException e) {
e.printStackTrace();
}
ground_y = 500;
}
// 地面动画方法
public void move() {
ground_x--;
if (ground_x < -110) {
ground_x = 0;
}
}
}
// 钢管类
class Column {
int column_x, column_y; // 钢管的中心坐标
int width, height; // 宽度高度
int gap = 140; // 钢管的空隙
Random random = new Random();
; // 随机坐标
BufferedImage coBufferImage; // 钢管图片
public Column(int x) {
super();
File coImage = new File("images/column.png");
try {
coBufferImage = ImageIO.read(coImage);
} catch (IOException e) {
e.printStackTrace();
}
column_x = x;
column_y = random.nextInt(180) + 150;
width = coBufferImage.getWidth();
height = coBufferImage.getHeight();
}
// 钢管动画方法
public void move() {
column_x--;
if (column_x < -width / 2) {
column_y = random.nextInt(180) + 150;
column_x = 432 + width / 2;
}
}
}
// 鸟类
class Bird {
int bird_x = 60, bird_y = 300; // 鸟的中心点坐标
int bird_width, bird_height; // 鸟的宽度,高度
double speed = 20; // 速度
double g = 4; // 加速度
double s; // 运动距离
double t = 0.3; // 运动时间
BufferedImage biBufferImage; // 鸟图片
BufferedImage[] images = new BufferedImage[8];
int bird_icon = 0;
public Bird() {
super();
for (int i = 0; i < images.length; i++) {
File biImage = new File("images/" + i + ".png");
try {
images[i] = ImageIO.read(biImage);
} catch (IOException e) {
e.printStackTrace();
}
}
biBufferImage = images[0];
bird_width = biBufferImage.getWidth();
bird_height = biBufferImage.getHeight();
}
// 小鸟展翅动画方法
int index = 0;
public void change() {
index++;
biBufferImage = images[index / 3 % 8];
}
// 小鸟移动的方法
double ratation; // 倾斜角度
public void move_go() {
double v0 = speed;
s = v0 * t - 0.5 * g * t * t;
double vt = v0 - g * t;
speed = vt;
bird_y = bird_y - (int) s;
ratation = s / 16;
if (bird_y <= bird_height / 2) {
bird_y = bird_height / 2;
}
}
// 重新飞翔
public void refly() {
speed = 20;
}
// 撞击地面
public boolean hit(Ground ground) {
return bird_y + bird_height / 2 >= ground.ground_y;
}
// 撞击钢管
public boolean hit(Column column) {
int left_x = column.column_x - column.width / 2 - bird_width / 2;
int right_x = column.column_x + column.width / 2 + bird_width / 2;
int top_y = column.column_y - column.gap / 2 + bird_height / 2 - 5;
int down_y = column.column_y + column.gap / 2 - bird_height / 2 + 5;
if (bird_x > left_x && bird_x < right_x) {
if (bird_y > top_y && bird_y < down_y) {
return false;
} else {
return true;
}
} else {
return false;
}
}
}
主要组成部分
Flappy Bird 游戏主要由以下几个核心组件构成:
-
游戏窗口(JFrame):作为游戏的主体框架,负责展示游戏内容并处理基本的窗口事件。
-
游戏画面(Sky 类):继承自 JPanel,负责游戏背景、小鸟、钢管和地面的绘制,以及游戏状态的管理。
-
小鸟(Bird 类):游戏的主角,通过玩家的点击或按键操作控制其上升和下落,模拟飞行行为。
-
钢管(Column 类):作为游戏中的障碍物,以固定间隔出现,玩家需要操控小鸟穿越钢管之间的空隙。
-
地面(Ground 类):在游戏画面的底部滚动,形成小鸟飞行的视觉参考。
关键功能
-
游戏循环:通过 Sky 类中的 action 方法实现,负责游戏状态的更新、绘制和重绘,以及游戏逻辑的循环执行。
-
事件监听:游戏监听鼠标和键盘事件,允许玩家通过点击或按键来控制小鸟的飞行。
-
碰撞检测:游戏实时检测小鸟与钢管或地面的碰撞,一旦发生碰撞,游戏结束。
-
得分机制:玩家每成功穿越一对钢管,得分增加。分数显示在游戏窗口的一角。
原作者对于游戏的基本功能已经实现,因此我将给游戏添加更多功能作为目标进行程序的改进
改进目标
我的改进目标主要集中在两个方面:
- 随着分数的提高,逐渐增加游戏速度,以提高游戏的挑战性。
- 让钢管的空隙大小随机变化,增加游戏的不可预测性,让玩家每次玩游戏时都有不同的体验。
加快游戏速度
首先,在 Sky 类中定义一个表示游戏速度的变量,此变量将用于控制地面、钢管以及小鸟的移动速度。
class Sky extends JPanel {
// 其他代码保持不变
private double speedMultiplier = 1.0; // 游戏速度倍数,初始为 1.0
// 其他方法保持不变
}
并且我们需要添加一个方法来根据当前分数调整 speedMultiplier。
public void updateGameSpeed() {
// 根据分数调整游戏速度,例如每增加10分,速度提升10%
speedMultiplier = 1.0 + score / 10 * 0.1;
}
接下来,我们需要修改 Ground 和 Column 类的 move 方法,使它们的移动速度能够根据 Sky 类中的 speedMultiplier 变量进行调整。
// Ground 类
public void move(double speedMultiplier) {
ground_x -= speedMultiplier;
if (ground_x < -110) {
ground_x = 0;
}
}
// Column 类
public void move(double speedMultiplier) {
column_x -= speedMultiplier;
if (column_x < -width / 2) {
column_y = random.nextInt(180) + 150;
column_x = 432 + width / 2;
}
}
然后,在 Sky 类中,当你调用这些 move 方法时,传递 speedMultiplier:
// Sky 类中的 action 方法或类似的更新逻辑部分
ground.move(speedMultiplier);
column.move(speedMultiplier);
column2.move(speedMultiplier);
这样,随着玩家分数的增加,游戏的难度也会逐渐提升,使游戏更具挑战性和趣味性。
随机的间隙大小
在 move 方法中更新 gap 的值,以便每次钢管重新出现时都有一个新的随机空隙:
public void move() {
column_x--;
if (column_x < -width / 2) {
column_y = random.nextInt(180) + 150;
column_x = 432 + width / 2;
// 每次钢管重新出现时更新空隙大小
this.gap = random.nextInt(51) + 100; // 例如,空隙大小介于 100 到 150 之间
}
}
结语
在本次探索和改进 Flappy Bird 游戏的旅程中,我们深入了解了游戏的基础代码结构,实现了两项关键的改进:随着玩家分数的增加逐步加快游戏速度,以及让钢管的空隙大小随机变化。这些改进不仅增加了游戏的挑战性,也为玩家带来了新鲜感和更多的乐趣。
通过这次经验,我们看到了即使对于简单的游戏,通过细微的调整也能大大增强游戏体验。这不仅考验了我们对游戏机制的理解,也锻炼了我们的编程技能,特别是在处理游戏逻辑和用户交互方面。
标签:java,游戏,column,width,小游戏,二次开发,new,bird,ground From: https://www.cnblogs.com/toner1ko/p/18049399