Java Swing 实现 Flappy Bird 小游戏完整源码
本文介绍如何使用 Java Swing 构建经典的 Flappy Bird(贪吃鸟)小游戏,详细解析了游戏窗口的初始化、键盘事件监听、图片资源加载、背景与管道动画逻辑以及碰撞检测算法,并提供完整的可运行源代码。
游戏核心设计与运行机制
本示例项目基于 Java AWT/Swing 框架实现,旨在通过图形用户界面(GUI)展示基础的游戏循环与交互逻辑。游戏的核心机制包括:
- 界面绘制:利用
Graphics对象在自定义的Frame窗口中绘制背景、地面、角色(布谷鸟)及障碍物(管道)。 - 状态控制:通过布尔变量
start(开始)、crash(碰撞中)和over(游戏结束)来控制不同的 UI 状态显示(如欢迎界面、游戏进行中、Game Over 提示)。 - 输入处理:绑定键盘事件,玩家通过按下
UP键控制小鸟上升,SPACE键开始游戏,ENTER键在失败后重新开始。 - 工具类封装:设计专门的工具类负责统一读取和管理游戏中的图片素材,确保资源加载的稳定性。
功能运行演示
以下是游戏不同阶段的视觉表现:
游戏初始界面,等待玩家按下空格键开始:
在游戏中,玩家通过按键控制小鸟的位置,使其向上飞起:
小鸟成功穿过上下移动的管道障碍物:
当小鸟撞到管道或超出边界时,触发 Game Over 画面:
代码实现详解
第 1 步:配置游戏主启动入口
目的:创建游戏窗口实例并建立主线程循环,以固定频率刷新屏幕图像,保证游戏动画流畅。
操作:定义 Main 类,调用 Cuckoo 类构造方法初始化窗口,并在 while(true) 循环中使用 Thread.sleep(100) 控制帧率。若未发生碰撞,则触发窗口重绘。
public class Main {
public static void main(String[] args) {
Cuckoo frame = new Cuckoo();
while (true){
try {
Thread.sleep(100);
if(!frame.crash){
frame.repaint();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
第 2 步:构建核心游戏逻辑与窗口交互
目的:继承 Frame 类,实现游戏的主逻辑层,包括坐标计算、绘制调度、事件监听和游戏状态重置。
参数说明:
- GAME_WIDTH, GAME_HEIGHT: 游戏区域尺寸。
- BIRD_X, BIRD_Y: 小鸟横坐标及动态纵坐标。
- SPEED: 背景和管道的移动速度。
- pipeRectangle1/2, birdRectangle: 用于碰撞检测的矩形对象。
关键实现逻辑:
- 双缓冲渲染:重写
update方法,先在内存中创建offScreenImage进行绘图,再一次性绘制到屏幕上,避免闪烁。 - 键盘监听:通过
addKeyListener捕获按键。按UP时减小bird_y(向上移动);按SPACE设置start=true;按ENTER执行reopen()重置分数和状态。 - 状态机管理:在
paint方法中,仅当start为真时才调用bird(),pipe()等更新函数,否则绘制“Ready”和“Start”欢迎图层。 - 碰撞检测:使用
Rectangle.intersects()判断小鸟矩形是否与上、下管道的矩形重叠,一旦重叠则标记crash=true和over=true。
核心类代码:
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
/**
* 1.窗口
* 2.背景
* 3.移动墙
* 4.布谷鸟
* 5.障碍物
* 6.背景音乐
* 7.音效
* 8.欢迎界面
* 9.game over
*/
public class Cuckoo extends Frame {
private int step;
private int score;
private int height;
private int moveLeft;
private int pipeMove;
boolean crash = false;
private boolean over = false;
private boolean start = false;
private static int bird_y = 270;
private static final int L_X = 800;
private static final int L_Y = 200;
private static final int SPEED = 2;
private static final int BIRD_X = 100;
private static final int GAME_X_Y_0 = 0;
private static final int GAME_WIDTH = 288;
private static final int GAME_HEIGHT = 512;
private static final int PIPE_WIDTH = ImageMgr.pipe.getWidth();
private static final int PIPE_HEIGHT = ImageMgr.pipe.getHeight();
private static final int READY_WIDTH = ImageMgr.ready.getWidth();
private static final int READY_HEIGHT = ImageMgr.ready.getHeight();
private static final int START_WIDTH = ImageMgr.start.getWidth();
private static final int BIRD_WIDTH = ImageMgr.bird[0].getWidth();
private static final int BIRD_HEIGHT = ImageMgr.bird[0].getHeight(); // 原文此处误写为getWidth,已修正逻辑含义
private static final int GROUND_HEIGHT = ImageMgr.ground.getHeight();
private static final int GAME_OVER_WIDTH = ImageMgr.gameOver.getWidth();
private static final int GAME_OVER_HEIGHT = ImageMgr.gameOver.getHeight();
private static final int BACKGROUND_HEIGHT = ImageMgr.background.getHeight();
private static Image offScreenImage;
private static Rectangle pipeRectangle1;
private static Rectangle pipeRectangle2;
private static Rectangle birdRectangle;
Cuckoo(){
setVisible(true);
setResizable(false);
//setTitle("cuckoo");
setBounds(L_X, L_Y,GAME_WIDTH,GAME_HEIGHT);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()){
case KeyEvent.VK_SPACE : start = true;break;
case KeyEvent.VK_ENTER : reopen();break;
}
}
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
bird_y -= 20;
}
}
});
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
@Override
public void update(Graphics g) {
if(offScreenImage == null){
offScreenImage = this.createImage(GAME_WIDTH,GAME_HEIGHT);
}
Graphics gOffScreen = offScreenImage.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.BLACK);
gOffScreen.fillRect(0,0,GAME_WIDTH,GAME_HEIGHT);
gOffScreen.setColor(c);
paint(gOffScreen);
g.drawImage(offScreenImage,0,0,null);
}
@Override
public void paint(Graphics g) {
background(g);
ground(g);
start(g);
if(start){
bird(g);
pipe(g);
colliedWith();
gameOver(g);
score(g);
}
}
//如果碰撞后,可以按enter键重开
private void reopen(){
if(crash){
score = 0;
over = false;
crash = false;
start = false;
moveLeft = 0;
pipeMove = 0;
//bird_y = 270;//继上次死的位置开始
}
}
private void score(Graphics g){
Font font = new Font("微软雅黑",Font.BOLD,18);
g.setFont(font);
Color c = g.getColor();
g.setColor(Color.lightGray);
g.drawString("score " + score,20,70);
g.setColor(c);
}
private void background(Graphics g){
g.drawImage(ImageMgr.background,
GAME_X_Y_0 - moveLeft,GAME_X_Y_0,
GAME_WIDTH - moveLeft,GAME_HEIGHT,
GAME_X_Y_0,
GAME_X_Y_0,
GAME_WIDTH,
GAME_HEIGHT, null);
g.drawImage(ImageMgr.background,
GAME_WIDTH-1 - moveLeft,GAME_X_Y_0,
GAME_WIDTH*2 - moveLeft, GAME_HEIGHT,
GAME_X_Y_0,
GAME_X_Y_0,
GAME_WIDTH,
GAME_HEIGHT, null);
moveLeft += SPEED;
//游戏启动计分
if(start && moveLeft % 100 == 0){
score += 1;
}
if(moveLeft >= GAME_WIDTH){
moveLeft = 0;
}
}
private void ground(Graphics g){
g.drawImage(ImageMgr.ground,0,BACKGROUND_HEIGHT - GROUND_HEIGHT,null);
}
private void bird(Graphics g){
Color c = g.getColor();
g.setColor(Color.BLUE);
g.drawRect(BIRD_X, bird_y,BIRD_WIDTH,BIRD_HEIGHT);
g.setColor(c);
g.drawImage(ImageMgr.bird[step++], BIRD_X, bird_y,null);
if(step >= ImageMgr.bird.length){
step = 0;
}
if(start){
bird_y += 4;
}
birdRectangle = new Rectangle(BIRD_X,bird_y,BIRD_WIDTH,BIRD_HEIGHT);
}
private void pipe(Graphics g){
drawPipe(g);
pipeMove += SPEED;
if(pipeMove >= GAME_WIDTH + PIPE_WIDTH){
pipeMove = 0;
Random ran = new Random();
height = ran.nextInt(10)*20;
drawPipe(g);
}
}
private void drawPipe(Graphics g){
g.setColor(Color.RED);
// g.drawRect(GAME_WIDTH - pipeMove,GAME_HEIGHT/5*3 - height,PIPE_WIDTH,PIPE_HEIGHT);
// g.drawRect(GAME_WIDTH - pipeMove,-GAME_HEIGHT/5*4 - height,PIPE_WIDTH,PIPE_HEIGHT);
g.drawImage(ImageMgr.pipe,GAME_WIDTH - pipeMove,GAME_HEIGHT/5*3 - height,null);
g.drawImage(ImageMgr.pipe,GAME_WIDTH - pipeMove,-GAME_HEIGHT/5*4 - height,null);
pipeRectangle1 = new Rectangle(GAME_WIDTH - pipeMove,GAME_HEIGHT/5*3 - height,PIPE_WIDTH,PIPE_HEIGHT);
pipeRectangle2 = new Rectangle(GAME_WIDTH - pipeMove,-GAME_HEIGHT/5*4 - height,PIPE_WIDTH,PIPE_HEIGHT);
}
private void gameOver(Graphics g){
if(over){
g.drawImage(ImageMgr.gameOver,GAME_WIDTH/2 - GAME_OVER_WIDTH/2, GAME_HEIGHT/2 - GAME_OVER_HEIGHT,null);
}
}
private void start(Graphics g){
if(!start){
g.drawImage(ImageMgr.ready,GAME_WIDTH/2 - READY_WIDTH/2, GAME_HEIGHT/2 - READY_HEIGHT,null);
g.drawImage(ImageMgr.start,GAME_WIDTH/2 - START_WIDTH/2, GAME_HEIGHT/2,null);
}
}
private void colliedWith(){
if(birdRectangle.intersects(pipeRectangle1) || birdRectangle.intersects(pipeRectangle2)){
crash = true;
over = true;
//gameOver(g);
}
}
}
第 3 步:实现图片资源加载工具类
目的:将游戏所需的静态图片和动画帧从业务逻辑中分离,提高代码可维护性。
操作:创建单例模式或静态加载的 ImageMgr 类,使用 ImageIO.read 读取 resources 目录下的 PNG 文件。
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Objects;
public class ImageMgr {
static BufferedImage background,ground,pipe,gameOver,start,ready;
static BufferedImage[] bird = new BufferedImage[3];
private static final ImageMgr INSTANCE = new ImageMgr();
//nothing
private ImageMgr(){
initImage();
};
private void initImage(){
try {
background = ImageIO.read(Objects.requireNonNull(Cuckoo.class.getClassLoader().getResourceAsStream("images/bg_light.png")));
ground = ImageIO.read(Objects.requireNonNull(Cuckoo.class.getClassLoader().getResourceAsStream("images/ground.png")));
pipe = ImageIO.read(Objects.requireNonNull(Cuckoo.class.getClassLoader().getResourceAsStream("images/pipe.png")));
gameOver = ImageIO.read(Objects.requireNonNull(Cuckoo.class.getClassLoader().getResourceAsStream("images/gameover.png")));
start = ImageIO.read(Objects.requireNonNull(Cuckoo.class.getClassLoader().getResourceAsStream("images/start.png")));
ready = ImageIO.read(Objects.requireNonNull(Cuckoo.class.getClassLoader().getResourceAsStream("images/ready.png")));
for(int i = 0;i < 3;i++){
bird[i] = ImageIO.read(Objects.requireNonNull(Cuckoo.class.getClassLoader().getResourceAsStream("images/blueBird_" + (i+1) + ".png")));
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static ImageMgr getInstance(){
return INSTANCE;
}
}
总结
通过实现《布谷鸟闯关》这一经典小游戏,深入掌握了 Java 图形化编程的核心要点。本项目涵盖了面向对象的思想实践,包括类的封装与继承(如继承 Frame),以及对数组、基本数据类型和流程控制的综合运用。对于希望学习 Java Swing 或了解简单游戏引擎底层逻辑的开发人员来说,这是一个很好的入门案例。以上就是Java实现经典游戏Flappy Bird的示例代码的详细内容,更多关于Java Flappy Bird游戏的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









