Java Swing 实现 Flappy Bird 小游戏完整源码

阿澈
阿澈
发布时间:2026-10-13| 6人浏览过
原创

本文介绍如何使用 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: 用于碰撞检测的矩形对象。

关键实现逻辑:

  1. 双缓冲渲染:重写 update 方法,先在内存中创建 offScreenImage 进行绘图,再一次性绘制到屏幕上,避免闪烁。
  2. 键盘监听:通过 addKeyListener 捕获按键。按 UP 时减小 bird_y(向上移动);按 SPACE 设置 start=true;按 ENTER 执行 reopen() 重置分数和状态。
  3. 状态机管理:在 paint 方法中,仅当 start 为真时才调用 bird(), pipe() 等更新函数,否则绘制“Ready”和“Start”欢迎图层。
  4. 碰撞检测:使用 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游戏的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。