简介:基于 Qt 框架实现的 FlappyBird 小游戏完整源代码,面向正在完成 C++ 课程设计或入门 Qt 图形界面开发的读者。项目实现了点击屏幕令小鸟上升、松开后下落、飞行途中躲避障碍物的核心玩法,代码逻辑清晰,并包含分数统计与游戏状态管理,适合直接参照完成课设报告与答辩演示。资源包共含 80 个文件,以 12 个 cpp 源文件、11 个头文件为主体,另有 5 个 ui 界面文件、1 个 pro 工程文件与 1 个 qrc 资源文件,47 张 png 图片完整覆盖了天空背景、小鸟动画与管道障碍等美术素材,压缩后整体仅 263KB,可直接导入 Qt Creator 编译运行。项目在结构上将主窗口、绘图场景、数据模型分层组织,界面与逻辑分离,便于理解 Qt 的事件处理、定时器驱动、碰撞检测和资源加载机制。目前已有 103 人学习过,对想要快速搭建 Qt 小游戏框架、巩固 C++ 面向对象设计或完成课程设计的人来说,是一份完整且可直接上手的参考资料。
1. 基于 Qt 的 FlappyBird 课程设计:一份能直接跑起来的 C++ 小游戏源码
期末课程设计又撞上 FlappyBird 了?网上搜“qt c++ FlappyBird 源代码”,下载链接不少,但多半是单文件 demo 或者缺资源的半成品,解压之后编译报错能把自己绕晕。我这次拆的这份资源是一个完整的 Qt Widgets 工程:.pro工程文件、MainWindow窗体、Graphics绘图逻辑、Models数据类、.ui界面描述、.qrc资源打包全都在,解压后直接用 Qt Creator 打开就能构建,不需要额外拼文件。它的玩法是点击屏幕让小鸟上升,松手后自然下落,撞到障碍物就结束——规则和你玩过的 FlappyBird 一致,但代码结构和课程设计要求的“分层清晰”对得上号。适合三类人:正在做 C++ 课程设计、需要一份能讲清楚原理的参考工程的同学;刚学 Qt 没多久、想看看 QTimer 驱动游戏循环和 QPainter 绘图怎么配合的新手;以及想快速改造一个完整小游戏、练手事件处理和碰撞检测的从业者。这篇笔记会把源码结构、Qt 版本选型、编译参数和常见报错一次讲透。
2. 源码结构与工程组织:FlappyBird.pro 到 Resources.qrc 的完整链路
拿到压缩包解压后,第一件事不是急着点编译,而是把目录结构捋一遍。这份资源用的是 qmake 工程体系,不是 CMake,所以入口是FlappyBird.pro文件。Sources目录下是Main.cpp、MainWindow.cpp、Graphics.cpp,Headers下对应MainWindow.h、Graphics.h,Forms里是MainWindow.ui,Resources里是Resources.qrc和Images图片目录。.gitignore和README.md也在,说明作者是按正规工程习惯组织的。
2.1 工程文件拆解:qmake 的.pro到底写了什么
.pro文件是 qmake 的构建脚本,它决定了编译哪些源文件、链接哪些模块、生成什么目标。常见写法是这个套路:
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = FlappyBird TEMPLATE = app DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ Sources/main.cpp \ Sources/MainWindow.cpp \ Sources/Graphics.cpp \ Sources/Models/Bird.cpp HEADERS += \ Headers/MainWindow.h \ Headers/Graphics.h \ Headers/Models/Bird.h FORMS += \ Forms/MainWindow.ui RESOURCES += \ Resources/Resources.qrc逻辑说明:QT += core gui是 Qt 基础模块,greaterThan(QT_MAJOR_VERSION, 4): QT += widgets是 Qt5 之后必须的写法,否则QWidget、QPainter这类 GUI 类找不到定义。TARGET决定生成的可执行文件名,TEMPLATE = app表示生成应用程序而不是库。SOURCES、HEADERS、FORMS、RESOURCES分别登记源码、头文件、UI 文件和资源文件。
参数说明:如果你的项目里新增了一个Pipe.cpp文件,必须同步加进SOURCES,否则编译能过但链接阶段会报“undefined reference”。这是 Qt 新手最常见的翻车点。另外DEFINES += QT_DEPRECATED_WARNINGS只是编译期警告,不影响运行,想精简可以删掉。
2.2 UI 文件与资源系统的配合方式
MainWindow.ui是 Qt Designer 生成的 XML 描述文件,描述窗口尺寸、按钮位置、布局方式。这个项目里 UI 文件承担的是主窗口骨架,真正的游戏画面是后续用QPainter在Graphics类里绘制的。理解这个分工很重要:.ui定的是“壳”,Graphics.cpp画的是“肉”。
Resources.qrc是资源打包文件,把所有图片资源编译进可执行文件:
<RCC> <qresource prefix="/"> <file>Images/bird.png</file> <file>Images/pipe.png</file> <file>Images/background.png</file> </qresource> </RCC>逻辑说明:prefix="/"表示资源在根路径下,代码里用:/Images/bird.png就能访问。这种方式的优势是程序发布时不用单独带图片目录,所有资源都在 exe 里,对课程设计答辩演示特别省心——拷到哪台机器都能跑。
参数说明:图片路径是相对.qrc文件所在目录的。Resources.qrc放在Resources文件夹里,所以图片路径写成Images/bird.png而不是Resources/Images/bird.png。如果编译后图片加载不出来,先检查这里。
这份工程的文件组织方式对课程设计答辩很友好。老师问“你的项目是怎么组织的”,你可以直接指着目录结构讲:Models放数据类、Graphics放绘制逻辑、Forms放界面描述。分层意识在 C++ 课程设计里是加分项,这份资源已经把骨架搭好了。
3. 游戏循环与碰撞检测:QTimer、事件处理与 QPainter 绘制的配合
FlappyBird 的核心机制不复杂:小鸟受重力影响持续下落,点击屏幕后获得一个向上的瞬时速度,障碍物从右向左匀速移动,撞到障碍物或地面就结束。难点在于“游戏循环”如何用 Qt 的事件模型实现,而不是像控制台程序那样用while(true)死循环。
3.1 主循环的搭建:QTimer 就是你的游戏心跳
Qt 是事件驱动框架,主线程不能阻塞,所以游戏循环要用QTimer定时触发刷新:
// MainWindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 初始化游戏对象 bird = new Bird(); scene = new Graphics(this); // 驱动循环的定时器:每 16ms 触发一次,约 60 FPS timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MainWindow::updateGame); timer->start(16); setCentralWidget(scene); } void MainWindow::updateGame() { bird->applyGravity(); // 小鸟受重力下落 bird->flapIfNeeded(); // 如果有点击事件,给小鸟一个上升冲量 scene->update(); // 触发重绘 }逻辑说明:QTimer的timeout信号每 16ms 发一次,每次触发就更新游戏状态然后让scene重绘。这样游戏逻辑和绘制逻辑是分离的——updateGame()里改的是数据,paintEvent()里根据数据画界面。数据与显示分离是 Qt 绘图的基本功,也是这份工程最值得抄的写法。
参数说明:timer->start(16)的 16ms 对应约 60FPS。想调难度,改的是bird->applyGravity()里的重力常量,而不是改定时器频率——定时器频率受系统调度影响,不精确,不适合做难度参数。
3.2 点击事件与小鸟物理模型
鼠标点击的处理是重写mousePressEvent,这是一个事件回调,不是轮询:
void MainWindow::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { bird->flap(); } QMainWindow::mousePressEvent(event); }// Models/Bird.cpp 中体现了简化物理模型 void Bird::applyGravity() { vy += gravity; // 速度累加重力加速度 y += vy; // 位置累加速度 } void Bird::flap() { vy = -jumpVelocity; // 点击瞬间,速度设为向上的负值 }逻辑说明:物理模型用的是最简单的欧拉积分——vy += gravity让速度每帧增加一个固定值,模拟重力加速度;y += vy让位置随速度变化。点击时直接把vy设为负值,也就是给一个向上的瞬时的冲量。这个模型足够还原 FlappyBird 的手感,又不会引入复杂物理公式。
参数说明:gravity和jumpVelocity是两个关键调参变量。gravity太大小鸟掉落像石头,jumpVelocity太大点击一次飞出去老远。经验值范围是gravity在0.3~0.5、jumpVelocity在-8~-6,具体数值取决于你的窗口高度和定时器频率。
3.3 障碍物生成与碰撞判定
障碍物是成对出现的上下管道,中间留一个缝隙。管道从右侧向左移动,移出屏幕后回收复用:
void Graphics::updatePipes() { // 每根管道向左移动固定步长 for (Pipe &pipe : pipes) { pipe.x -= pipeSpeed; } // 移出左侧屏幕的管道移除,右侧生成新管道 if (!pipes.isEmpty() && pipes.first().x + pipeWidth < 0) { pipes.removeFirst(); } if (pipes.isEmpty() || pipes.last().x < sceneWidth - pipeSpacing) { createPipe(); } } bool Graphics::checkCollision(const Bird &bird) { QRect birdRect = bird.rect(); // 超出上下边界直接判死 if (birdRect.top() <= 0 || birdRect.bottom() >= groundHeight) { return true; } // 与每根管道做矩形相交检测 for (const Pipe &pipe : pipes) { QRect topPipeRect(pipe.x, 0, pipeWidth, pipe.gapY - gapHeight / 2); QRect bottomPipeRect(pipe.x, pipe.gapY + gapHeight / 2, pipeWidth, groundHeight - pipe.gapY); if (birdRect.intersects(topPipeRect) || birdRect.intersects(bottomPipeRect)) { return true; } } return false; }逻辑说明:碰撞检测用的是 Qt 自带的QRect::intersects,把小鸟和管道都抽象成矩形做相交判断。上下边界的判断要放在矩形碰撞之前——如果小鸟已经飞出屏幕,矩形碰撞的结果没有意义。gapY是管道缝隙的中心位置,gapHeight是缝隙大小,这两个参数直接决定游戏难度。
参数说明:缝隙宽度gapHeight建议在120~160像素之间,小于 100 基本玩不过去,大于 200 又太简单。管道间距pipeSpacing控制在250~350像素,太密没有反应时间,太疏节奏太慢。
这里有个容易被忽略的细节:QTimer驱动的游戏循环天然存在帧率不稳定的问题。如果系统繁忙,timeout间隔会变长,小鸟的物理模拟就会变慢。严谨的做法是记录两次timeout之间的真实时间差dt,用dt修正位移量。但这个项目是课程设计,固定步长已经够用,答辩时能说出这个改进方向反而是加分项。
4. 编译配置与运行环境:从 Qt 5.15 到 Qt 6 的兼容性处理
源码能跑起来才算数。这一章解决“拿到工程后怎么编译”的问题,包括 Qt 版本选择、编译器套件配置、资源路径检查三个环节。
4.1 Qt 版本与编译器套件的选择
这份工程的.pro文件是按 Qt 5 的写法组织的,Windows 下推荐用 Qt 5.15.2 LTS 版本,这是一个非常稳定的长期支持版本。官方安装包通常包含多个编译器套件,常见的是MSVC 2019 64-bit和MinGW 64-bit两套:
| 套件 | 编译器 | 适用场景 | 注意点 |
|---|---|---|---|
| MSVC 2019 64-bit | cl.exe | Windows 原生开发,性能好 | 需要 Visual Studio 2019/2022 的 C++ 组件 |
| MinGW 64-bit | g++ | 轻量级,不需要 VS | 生成的 exe 依赖 MinGW 运行时库 |
选择原则很简单:你机器上装了 Visual Studio 就选 MSVC 套件,没装就选 MinGW 套件。两者生成的 exe 不能混用——MSVC 编译的程序不能在 MinGW 环境跑,反之亦然。
4.2 打开与构建工程的完整步骤
命令行构建和 Qt Creator 图形界面构建是两种常见做法。Qt Creator 的操作流程是:
- 打开 Qt Creator,选择
File->Open File or Project,选中FlappyBird.pro - 选择配置好的 Qt 版本和编译器套件,点击
Configure Project - 点击左下角的绿色三角按钮构建并运行
命令行构建更直接,适合熟悉终端操作的人:
mkdir build cd build qmake ..\FlappyBird.pro mingw32-make参数说明:qmake会根据.pro文件生成对应平台的 Makefile,mingw32-make执行编译和链接。如果你的环境是 MSVC,最后一步换成nmake。构建成功后在当前目录下会生成FlappyBird.exe。
逻辑说明:qmake是 Qt 特有的构建工具,它和 CMake 是两种不同的构建体系。你觉得 CMake 更通用,但这份工程就是 qmake 的,直接用 qmake 构建最省事,不用手动改写 CMakeLists.txt。
4.3 高版本 Qt 的兼容性调整
如果你机器上装的是 Qt 6,直接打开会报一堆错误。主要原因是 Qt 6 里QPainter的高精度坐标 API 变了,还有部分类的位置调整过。常见的兼容性修改是这两处:
// Qt 5 写法(工程原始代码) painter->drawText(birdPos, QString("Score: %1").arg(score)); // Qt 6 兼容写法 painter->drawText(birdPos.toPoint(), QString("Score: %1").arg(score));逻辑说明:Qt 5 里QPainter::drawText的重载函数接受QPointF,但 Qt 6 收紧了接口,要求显式传入QPoint或QRect。如果你的编译报错指向drawText的重载不匹配,加上.toPoint()转换就行。
参数说明:另一个 Qt 6 的坑是QRandomGenerator替代了旧的qrand()。如果你在源码里看到qrand()报错,改成QRandomGenerator::global()->bounded(max)取值。
4.4 资源加载失败的排查路径
如果程序能编译通过但窗口里图片不显示,问题大概率出在.qrc文件上。检查顺序三步走:先确认.qrc里的路径是相对路径而不是绝对路径;再确认图片文件名的大小写和实际文件一致,Windows 不区分大小写但 Qt 资源的索引是敏感的;最后看图片格式,.png和.jpg都支持,但某些.bmp的透明通道处理在 Qt 里表现不稳定。
5. 编译与运行避坑指南:五个高频报错的诊断与解决
这条路我走过不止一次。以下五条是拿到这份源码后最可能踩中的坑,每条都是真实场景,按“现象 → 原因 → 解决”写清楚。
5.1 现象::-1: error: dependent '..\..\qt\5.15.2\msvc2019_64\include\QtWidgets' does not exist
原因:这个报错非常典型——Qt Creator 里选的编译器套件和你安装的 Qt 库不匹配。报错信息里的msvc2019_64路径说明当前套件是 MSVC 编译的 Qt 库,但你用的编译器可能是 MinGW,或者反过来。Qt 的库是分编译器编译的,MSVC 版的库只能在 MSVC 的环境下链接。
解决:在 Qt Creator 的Tools->Options->Kits里检查当前 Kit 的“Compiler”和“Qt version”是否匹配。MSVC 套件配 MSVC 版 Qt,MinGW 套件配 MinGW 版 Qt,不要跨编译器混用。
5.2 现象:undefined reference to MainWindow::MainWindow(QWidget *)链接失败
原因:.pro文件里的SOURCES没包含MainWindow.cpp,或者文件名拼写有误。qmake 是按.pro里登记的源文件列表去编译的,漏掉一个就会在链接阶段找不到符号。
解决:打开.pro文件,确认SOURCES +=那几行包含所有.cpp文件;如果你新增了Pipe.cpp之类的文件,手动补上这一行,重新执行 qmake(Qt Creator 里右键工程根目录选Run qmake)再构建。
5.3 现象:窗口显示出来但游戏画面完全空白
原因:游戏窗口空白通常是两个原因叠加——QTimer没启动,或者paintEvent里QPainter没调用begin/end配对。很多初学者实现绘制时写了painter.begin(this)但忘了painter.end(),或者反过来,导致绘制状态错乱。
解决:检查MainWindow构造函数里timer->start(16)是否执行到;再看Graphics::paintEvent里QPainter painter(this)的构造方式是否正确。这个项目用的是QPainter painter(this)构造函数自动绑定画布的方式,不需要手动begin/end。
5.4 现象:图片加载不出来,QPixmap is null
原因:资源路径写错了。直接写:/bird.png但.qrc里的实际路径是:/Images/bird.png,少了一层目录就找不到。另一种常见情况是.qrc文件没有在.pro里登记,资源根本没有被编译进去。
解决:用RCC结构检查.qrc的prefix和<file>路径。如果prefix是/,那么:/Images/bird.png就是正确的访问路径。可以先用绝对路径排错,确认图片文件本身没问题后,再回头查.qrc配置。
5.5 现象:高分辨率屏上游戏窗口显示异常,字体重影
原因:Qt 5.15 之前的高 DPI 缩放需要手动启用,如果你的程序没设置Qt::AA_EnableHighDpiScaling或没有在.pro里加QT += widgets的HIGHDPI处理,在 Windows 缩放比例超过 100% 时就会出现模糊或错位。
解决:在main.cpp的QApplication构造之前加两行:
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);逻辑说明:这两行让 Qt 在启动时自动适配系统缩放比例,AA_EnableHighDpiScaling处理逻辑坐标到物理坐标的映射,AA_UseHighDpiPixmaps让图片资源在缩放时保持清晰。注意这两行必须在QApplication实例创建之前调用,否则不生效。
6. 改造与发布:把课程设计做成能答辩的完整项目
源码能跑只是第一步,课程设计想要拿高分,得让老师看到“我不仅做出来了,还知道怎么改进”。
6.1 计分系统与游戏状态机的实现思路
原版 FlappyBird 每次穿过一对管道得一分。当前工程的核心循环在updateGame()里,改造计分的关键是检测“小鸟穿过了管道的判定线”这个事件,而不是每帧加分。常见做法是给每根管道加一个scored标志位,当小鸟的x坐标超过管道中心且scored == false时,分数加一,scored置为true:
void Graphics::updateScore(Bird &bird, int &score) { for (Pipe &pipe : pipes) { if (!pipe.scored && bird.x() > pipe.x + pipeWidth / 2) { score++; pipe.scored = true; } } }参数说明:pipe.scored是关键标志位,防止同一根管道被重复计分。按这个思路扩展,你还可以在Bird类里加state枚举(Ready、Flying、Dead),用状态机控制游戏开始、运行、结束三个阶段——这是课程设计答辩里老师最喜欢听到的“软件工程设计思想”。
6.2 音效与 QSoundEffect 的接入
FlappyBird 的反馈感很大程度来自音效。Qt 里接入音效用QSoundEffect最轻量:
QSoundEffect *flapSound = new QSoundEffect(this); flapSound->setSource(QUrl("qrc:/Sounds/flap.wav")); flapSound->setVolume(0.5f); flapSound->play();逻辑说明:QSoundEffect适合播放短音效,它底层是异步播放,不会阻塞 UI 线程。注意音频文件也要加进.qrc才能用qrc:路径访问。如果不想引入音效文件,也可以用一个更取巧的方案:用QApplication::beep()或QApplication::alert()做系统提示音,但效果粗糙,不推荐用于正式项目。
6.3 发布前必须检查的清单
答辩演示往往是用老师的电脑或投影机,环境不可控。每次发布前,我习惯强制走一遍这样的检查流程,顺序不能乱:
- 构建模式切到
Release,Qt Creator 左下角的“构建”按钮旁边可以选择 Debug/Release,Release 版本体积小、运行快,不依赖调试库 - 用
windeployqt工具部署运行库到 exe 所在目录,命令是windeployqt FlappyBird.exe,它会自动把需要的 Qt DLL 拷贝过来,防止目标机器没装 Qt - 确认资源和图片都打包进去了——
windeployqt只处理 DLL,不处理你的图片,所以.qrc必须是编译进来的而不是外部读取 - 换一台没有装 Qt 的干净机器跑一遍,验证部署是否完整;如果闪退,优先看缺哪个 DLL,用
Dependencies工具检查
部署这步是课程设计答辩翻车的重灾区。很多同学在开发机上跑得好好的,一换电脑就启动报“找不到 Qt5Core.dll”,就是因为没做 windeployqt。从那以后我每次替别人看 Qt 项目,第一件事就是检查 exe 同级目录的 DLL 是否齐整,再决定要不要上机测试。这份 FlappyBird 源码本身没这个问题,但你的改造版本很可能遇到,希望帮到你。
本文还有配套的精品资源,点击获取