简介:这是一份面向高校C++课程设计场景的Qt实战项目源码,适合正在学习Qt框架、需要完成课程作业或想通过小游戏案例巩固C++与图形界面开发的学生参考。项目完整还原了经典FlappyBird玩法:点击屏幕控制小鸟向上飞行,避开障碍物,涵盖游戏循环、碰撞检测、事件响应与界面绘制等核心逻辑。压缩包共80个文件,约263KB,其中47个png图片资源用于小鸟、管道与背景素材,12个cpp与11个h文件构成游戏主逻辑、图形渲染与模型类,5个ui文件配合qrc资源描述完成界面布局,另有pro工程文件与README说明,目录按Sources、Headers、Forms、Resources等模块划分,结构清晰便于阅读与二次开发。目前已有103人学习下载,可作为课程设计参考模板,帮助读者快速理解Qt项目组织方式、信号槽机制与游戏状态管理,并在此基础上进行功能扩展与界面美化。
1. 从一份 FlappyBird 源码说起:Qt 课程设计到底该做成什么样
很多人对 C++ 课程设计的想象还停留在控制台里打印九九乘法表,或者用cin/cout做一个图书管理系统。但如果你手里拿到的题目是「基于 Qt 制作的经典小游戏 FlappyBird 源代码」,那这其实是一次相当完整的桌面 GUI 工程训练:窗口系统、事件循环、定时器驱动、碰撞检测、资源管理、状态机,一个都不少。FlappyBird 的规则简单到一句话能说清——点击让小鸟上升,穿过管道缝隙,撞到就结束——但把它用 Qt 真正跑起来,你会遇到刷新率、坐标系、信号槽连接、图片资源路径这些实打实的问题。
这份源码适合两类人:一类是正在做 C++/Qt 课程设计、需要一个能跑通、能讲清楚、能改出自己东西的参考实现;另一类是想从「会写 C++ 语法」过渡到「能做一个有界面的小游戏」的初学者。它不追求 3D 渲染或物理引擎,核心就是QWidget重写paintEvent、QTimer驱动帧更新、QPainter画图、矩形相交做碰撞。把这几件事吃透,你后面做任何 2D 小游戏都是同一套骨架。下面我按「先能跑、再讲清、最后避坑」的顺序,把这份 FlappyBird 源码拆开讲。
2. 环境搭建与工程骨架:让 FlappyBird 源码在你机器上先跑起来
2.1 Qt 版本与编译器怎么选才不翻车
拿到一份 Qt 源码,第一件事不是读代码,而是确认它能在你的环境里编译。FlappyBird 这类小游戏用到的都是 Qt 的基础模块(QtWidgets、QtGui、QtCore),对版本不敏感,但编译器必须和 Qt 套件匹配。常见做法是:Windows 上用 Qt 5.15.2 配 MinGW 或者 MSVC,Linux 上用系统包管理器装的 Qt5,macOS 上用官方在线安装器。热搜里频繁出现的qt 5.15.2下载安装、qt安装、qt官网下载,说明版本选择确实是新手第一道坎。
我一般会推荐 Qt 5.15.2 + MinGW 的组合,原因是它自带 MinGW 编译器,装完就能用,不需要额外配 Visual Studio。如果你已经装了 Visual Studio,那用 MSVC 套件也行,但要注意 Qt 套件和编译器位数必须一致,32 位 Qt 配 32 位编译器,混了就会在链接阶段报一堆找不到符号的错误。安装时记得勾选Qt 5.15.2下的MinGW 8.1.0 64-bit和Qt Creator,这两个是必须的。
提示:安装路径不要带中文和空格,Qt 的构建系统对路径里的特殊字符处理得并不好,这是很多人第一次编译就失败的原因。
2.2 用 Qt Creator 打开工程并跑通第一帧
拿到源码后,目录里通常有一个.pro文件,这是 qmake 工程的入口。用 Qt Creator 打开它,选择对应的套件(Kit),然后点左下角的绿色三角构建并运行。如果一切正常,你会看到一个窗口,小鸟开始下落,点击鼠标或按空格小鸟上升。
如果编译报错,先看错误类型。最常见的是两类:一是找不到头文件,说明.pro里的QT +=没写全;二是找不到图片资源,说明资源文件(.qrc)没被正确加载。一个典型的.pro文件长这样:
QT += core gui widgets TARGET = FlappyBird TEMPLATE = app SOURCES += main.cpp \ gamewidget.cpp \ bird.cpp \ pipe.cpp HEADERS += gamewidget.h \ bird.h \ pipe.h RESOURCES += res.qrc这段配置里,QT += core gui widgets声明了依赖的 Qt 模块,TEMPLATE = app表示生成可执行程序,SOURCES和HEADERS列出源文件和头文件,RESOURCES把图片、音效等资源打包进可执行文件。参数上唯一需要你改的是TARGET,也就是生成的可执行文件名。如果你新增了类文件,记得手动加进SOURCES和HEADERS,qmake 不会自动扫描目录。
2.3 资源文件与图片路径:为什么你的小鸟是白块
FlappyBird 依赖几张图片:小鸟、管道、背景、地面。这些图片通常放在res.qrc里统一管理。.qrc是一个 XML 格式的资源清单,写法如下:
<RCC> <qresource prefix="/"> <file>images/bird.png</file> <file>images/pipe.png</file> <file>images/background.png</file> <file>images/ground.png</file> </qresource> </RCC>prefix="/"定义了资源访问前缀,代码里用":/images/bird.png"就能取到图片。如果你运行后看到的是白色方块而不是小鸟,八成是资源路径写错了,或者.qrc没有加入RESOURCES。排查方法是:在paintEvent里打印QPixmap的isNull(),如果返回 true,说明图片没加载成功。
注意:图片路径区分大小写,Windows 上可能不敏感,但换到 Linux 就会直接失败,写资源路径时统一用小写最稳妥。
3. 核心机制拆解:定时器、事件与碰撞检测怎么落地
3.1 QTimer 驱动游戏循环:帧率与刷新节奏
FlappyBird 的本质是一个不断重绘的循环:每隔一段时间更新小鸟位置、管道位置,然后重绘整个窗口。Qt 里实现这个循环的标准做法是用QTimer,设置一个固定间隔,超时信号连接到更新槽函数。
// gamewidget.cpp 构造函数中 timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &GameWidget::updateGame); timer->start(16); // 约 60 FPStimer->start(16)里的 16 是毫秒,1000/16 约等于 62.5,接近 60 帧每秒。这个值不能太小,太小会让 CPU 占用飙升;也不能太大,太大小鸟移动会一顿一顿的。我一般用 16 或 20,前者更顺滑,后者更省资源。updateGame()里做三件事:更新小鸟的垂直速度、更新管道水平位置、调用update()触发重绘。
void GameWidget::updateGame() { if (gameOver) return; // 重力加速度 birdVelocity += gravity; birdY += birdVelocity; // 管道左移 for (auto &pipe : pipes) { pipe.x -= pipeSpeed; } // 碰撞检测 checkCollision(); // 触发重绘 update(); }gravity和pipeSpeed是两个关键参数。gravity控制小鸟下落的加速度,典型值在 0.5 到 1.0 之间;pipeSpeed控制管道移动速度,典型值在 2 到 4 之间。这两个值直接决定游戏难度,调大了就是「地狱模式」,调小了就是「养老模式」。建议先按 0.6 和 3 起步,跑起来之后再微调。
3.2 鼠标与键盘事件:点击上升的响应逻辑
FlappyBird 的操作只有一个:点击或按空格让小鸟获得一个向上的速度。在 Qt 里,鼠标事件重写mousePressEvent,键盘事件重写keyPressEvent。
void GameWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { birdVelocity = jumpForce; // 向上速度,负值 } } void GameWidget::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Space) { birdVelocity = jumpForce; } }jumpForce是一个负值,因为 Qt 的坐标系原点在左上角,y 轴向下为正。典型值在 -8 到 -12 之间。这里有个容易忽略的点:如果游戏已经结束,点击不应该再让小鸟跳。所以要在事件处理里加if (gameOver) return;,否则会出现「死了还能动」的玄学现象。
3.3 碰撞检测:矩形相交与边界判断
碰撞检测是 FlappyBird 的核心逻辑。小鸟和管道都可以抽象成矩形,用QRect的intersects()方法判断是否相交。
void GameWidget::checkCollision() { QRect birdRect(birdX, birdY, birdWidth, birdHeight); // 撞地面或天花板 if (birdY + birdHeight >= groundY || birdY <= 0) { gameOver = true; return; } // 撞管道 for (const auto &pipe : pipes) { QRect topPipe(pipe.x, 0, pipeWidth, pipe.gapY); QRect bottomPipe(pipe.x, pipe.gapY + gapHeight, pipeWidth, groundY - pipe.gapY - gapHeight); if (birdRect.intersects(topPipe) || birdRect.intersects(bottomPipe)) { gameOver = true; return; } } }birdRect是小鸟的包围盒,topPipe和bottomPipe分别是上下两根管道的矩形。gapY是缝隙上沿的 y 坐标,gapHeight是缝隙高度。这里有个细节:小鸟的包围盒通常要比图片略小一圈,因为图片边缘有透明像素,用完整图片尺寸做碰撞会让玩家觉得「明明没碰到却死了」。我一般会把birdWidth和birdHeight各减 4 到 6 像素。
提示:碰撞检测放在
updateGame()里每帧执行一次就够了,不要放在paintEvent里,绘制函数里做逻辑判断是典型的职责混淆。
4. 避坑与排查:FlappyBird 源码编译运行中的 5 个高频问题
4.1 报错 cannot mix incompatible Qt library
这个报错在热搜里出现过,完整信息通常是fatal: cannot mix incompatible Qt library (version 0x50601) with this library。现象是程序启动直接崩溃,控制台打印这行字。原因是你的系统里存在多个 Qt 版本,运行时链接到了错误的库。比如你编译用的是 Qt 5.15.2,但环境变量PATH里优先找到了另一个版本的 Qt 动态库。
解决办法是清理环境变量,确保PATH里只有目标 Qt 版本的bin目录。在 Qt Creator 里运行时,可以在「项目」→「运行」→「环境」里检查PATH设置。如果是在命令行运行,用ldd(Linux)或Dependency Walker(Windows)查看可执行文件实际链接的 Qt 库路径。
4.2 图片加载失败导致白块或崩溃
现象是窗口能打开,但小鸟、管道都是白色方块,或者程序直接崩溃。原因通常是.qrc文件没有加入.pro的RESOURCES,或者资源路径大小写不匹配。排查方法是在代码里加一行qDebug() << QPixmap(":/images/bird.png").isNull();,如果输出 true,说明资源没找到。
解决方式是检查.qrc里的<file>路径是否和实际文件路径一致,然后确认.pro里有RESOURCES += res.qrc。改完.qrc后需要重新构建,Qt Creator 有时候不会自动检测资源变化,手动点「构建」→「重新构建」最保险。
4.3 定时器不触发或游戏卡顿
现象是窗口打开了,但小鸟不动,或者动了几下就卡住。原因可能是QTimer没有start(),或者updateGame()里做了耗时操作阻塞了事件循环。另一个常见原因是connect写错了,信号槽没连上。
检查步骤:先在updateGame()第一行加qDebug() << "tick";,看控制台是否持续输出。如果没有输出,说明定时器没启动或信号槽没连上。如果有输出但画面不动,说明update()没被调用,或者paintEvent里没画东西。connect的写法要确认参数类型匹配,Qt 5 之后推荐用函数指针语法connect(timer, &QTimer::timeout, this, &GameWidget::updateGame),编译期就能发现错误。
4.4 碰撞检测过于灵敏或迟钝
现象是玩家觉得「明明没碰到管道却死了」,或者「穿过去了却没死」。原因是碰撞矩形和实际图片尺寸不匹配。图片通常有透明边距,直接用图片宽高做碰撞框会偏大。
解决办法是手动调整碰撞矩形的宽高和偏移。比如小鸟图片是 40x30,实际可见部分可能只有 34x24,那就把碰撞框设成 34x24,并在绘制时做相应的偏移。管道同理,缝隙的gapHeight要略大于视觉上的缝隙,给玩家一点容错空间。这个参数没有标准值,需要你跑起来之后凭手感调,调到「感觉合理」为止。
4.5 发布版本缺少 DLL 无法运行
现象是在 Qt Creator 里跑得好好的,把 exe 拷到别的电脑就报「缺少 Qt5Core.dll」。原因是 Qt 程序依赖动态库,发布时需要把相关 DLL 一起带上。解决办法是用 Qt 自带的windeployqt工具(Windows)或macdeployqt(macOS)自动收集依赖。
# Windows 下在 Qt 命令行环境中执行 windeployqt FlappyBird.exe这条命令会把Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll以及平台插件platforms/qwindows.dll拷到 exe 同目录。注意platforms目录必须存在,否则程序启动会报could not find the Qt platform plugin。Linux 下可以用linuxdeployqt,思路是一样的。
5. 从能跑到能改:把 FlappyBird 源码变成你自己的课程设计
把源码跑通只是第一步,课程设计要拿得出手,你得能改出点自己的东西。我一般会从三个方向下手:加分数系统、加音效、加难度递增。分数系统最简单,管道通过时给score加一,然后在paintEvent里用QPainter::drawText画出来。音效需要QSoundEffect或QMediaPlayer,注意 Qt 5 和 Qt 6 的音频模块 API 有差异,用之前先确认版本。难度递增就是随着分数提高,逐渐增大pipeSpeed或缩小gapHeight。
void GameWidget::paintEvent(QPaintEvent *event) { QPainter painter(this); // 画背景 painter.drawPixmap(0, 0, backgroundPixmap); // 画管道 for (const auto &pipe : pipes) { painter.drawPixmap(pipe.x, 0, pipeWidth, pipe.gapY, pipePixmap); painter.drawPixmap(pipe.x, pipe.gapY + gapHeight, pipeWidth, groundY - pipe.gapY - gapHeight, pipePixmap); } // 画小鸟 painter.drawPixmap(birdX, birdY, birdWidth, birdHeight, birdPixmap); // 画分数 painter.setPen(Qt::white); painter.setFont(QFont("Arial", 24, QFont::Bold)); painter.drawText(20, 40, QString("Score: %1").arg(score)); }这段paintEvent是每帧都会执行的,所以里面不要做任何耗时计算。drawPixmap的重载版本可以指定目标矩形,实现缩放绘制。分数用drawText画在左上角,字体和颜色按自己喜好调。如果你想加「最高分」记录,可以用QSettings把分数存到本地,下次启动读出来。
验证你的改动是否生效,最直接的方法就是跑起来玩几局。分数有没有增加、音效有没有响、难度有没有变化,玩一把就知道了。如果改了代码没效果,先确认有没有重新构建,Qt Creator 有时候增量编译会漏掉改动,点「重新构建」能解决大部分玄学问题。
最后说个我自己的习惯:每次改完一个功能,先提交一次版本控制,哪怕只是本地 git。FlappyBird 这种小游戏改动频繁,没有后悔药的话,改崩了想回退都难。课程设计答辩的时候,老师大概率会问你「这个参数为什么设成这个值」,你得能说出个所以然,而不是「抄的」。把gravity、jumpForce、pipeSpeed这三个参数调一调,感受一下手感变化,你就真的理解这个游戏了。希望帮到你。
本文还有配套的精品资源,点击获取