ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C++ Qt跑酷游戏源码拆解:课设结构、QTimer与碰撞检测

C++ Qt跑酷游戏源码拆解:课设结构、QTimer与碰撞检测 简介这是一份基于C与Qt框架开发的跑酷小游戏完整源码属于大二课程设计中的高分项目评审得分99分。资源定向服务于计算机相关专业正在完成课程作业或期末大作业的学生也适合需要项目实战练习的入门至中级学习者可直接作为课程设计、期末大作业的参考实现。压缩包共72个文件约3.46MB包含7个cpp源码、6个头文件、2个UI界面设计文件以及项目管理文件pro和资源文件qrc另有53张png图片素材用于游戏角色与场景帧动画整体目录结构清晰可直接在Qt环境中打开运行。目前已有94人浏览学习。代码经过导师指导与认可逻辑完整、注释到位从角色移动、障碍物生成到碰撞检测均有实现帮助学习者快速理解Qt事件循环、绘图与动画机制并可作为课设项目直接参考、扩展或提交。1. 为什么这份 C Qt 跑酷源码值得拆99 分课设的结构感期末周的课程设计题发下来最尴尬的不是题目难而是手头只有控制台版的贪吃蛇撑不起一个带界面的程序。这份 C基于 Qt 框架的跑酷小游戏源码评审拿到 99 分我拆完发现它不止是能交差的 demo——场景切换、角色帧动画、障碍物刷新、飞镖攻击完整游戏链路都在而且纯 Qt Widgets 实现没引入任何游戏引擎。适合两类人一类是计算机专业正在写课程设计、想参考完整工程结构和答辩逻辑的学生另一类是刚开始做 Qt 开发、想练 QTimer 和键盘事件又不想从空白窗口起步的学习者。下文按我实际拆包顺序先看骨架再聊机制最后摊开编译和踩坑。2. 项目结构拆解先从五个核心类看懂 Qt 游戏工程这个源码包解压开后是一个非常规整的 Qt Widgets 工程running.pro、main.cpp、mainwindow、startscence、gamesence、character、barrier、feibiao外加一个 res 资源目录。第一次在 Qt Creator 里打开别急着点构建先顺着类名把职责边界理一遍。这个结构是典型的“场景与实体分离”——mainwindow 负责窗口壳gamesence 负责游戏世界character、barrier、feibiao 分别代表角色、障碍物和飞镖。答辩时老师问“为什么这么设计”可以直接回答实体不依赖窗口场景只管理实体关系后续加新障碍物不影响主流程。2.1 main.cpp一切从事件循环开始#include mainwindow.h #include QApplication int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }这段代码短到乍看像源码不全但它完整展示了 Qt 程序的启动纪律。QApplication 构造在 main 函数栈上管理整个应用生命周期MainWindow 实例化后通过 w.show() 显示出来最后 a.exec() 进入事件循环之后所有键盘事件、定时器事件、重绘事件都从 exec() 开始逐个分发。一个课设项目能把 main 压到这么短说明作者是有意识地把逻辑收进类里而不是在 main 里堆一堆全局变量和 while 循环。重点看 a.exec() 这一行。有些同学在 main 里写 while(1) 模拟游戏循环结果窗口一直转圈不刷新本质就是抢占了事件循环。这个项目把游戏循环全部交给 QTimermain 文件就彻底干净了后面第 3 章会详细说这个机制。课设答辩时这段代码可以直接背下来讲“事件循环”这个考点。2.2 gamesence 与 barrier游戏场景和障碍物的配合方式gamesence 是这份源码里最重的类承担四件事维护障碍物列表、维护飞镖列表、驱动角色帧动画、统一做碰撞判定。barrier 类是单个障碍物常见做法是每个 barrier 持有一个 QLabel 指针和一份坐标构造时把自己挂到 gamesence 的布局上gamesence 通过一个 QList 持有当前存活的所有障碍物每帧遍历调用移动逻辑。这里有一个 C 课设很容易踩的崩溃点遍历 QList 的同时直接 delete 元素会导致迭代器失效。我一般会给 barrier 加一个 m_active 标志碰撞后只把这个标志置成 false下一帧统一回收既安全又好讲答辩时还能顺势带出“延迟销毁”这个设计点。想扩展新障碍物只需要给 barrier 加一个类型枚举在主场景的生成逻辑里多写一个分支不用动主流程。2.3 mainwindow 与 startscence封装的界线画在哪开头我以为 mainwindow 是游戏窗口读完后发现它是“壳”。startscence开始场景持有标题贴图和开始按钮代码里有 mainwindow.ui 和 startscence.ui 两个界面文件说明原始项目是在 Qt Designer 里拖出来的界面。Qt Designer 拖完控件后由 uic 在编译期自动生成 C 代码所以你不要手动去改 ui 文件源码直接在 Form Editor 里拖控件就好。一个加分的细节是开始按钮的连接方式槽函数里先隐藏 startscence再 show 出 gamesence同时把焦点给到 gamesence。如果焦点还留在按钮上后面键盘事件会被按钮吃掉角色按方向键没反应。封装界线就画在这里——mainwindow 只负责场景切换不掺游戏逻辑gamesence 进去之后自己跑自己的 QTimer。答辩被问到模块职责时照着这个边界回答就是清晰的软件工程思路。2.4 running.pro 与 res.qrcQt 工程的两份隐形说明书QT core gui greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET running TEMPLATE app SOURCES main.cpp \ mainwindow.cpp \ startscence.cpp \ gamesence.cpp \ character.cpp \ barrier.cpp \ feibiao.cpp HEADERS mainwindow.h \ startscence.h \ gamesence.h \ character.h \ barrier.h \ feibiao.h FORMS mainwindow.ui \ startscence.ui RESOURCES res.qrcrunning.pro 是 qmake 的构建脚本决定编译哪些源文件、链接哪些模块。注意第二行QT widgets这是让工程跑在 Widgets 模块上的关键。Qt 5 以后core 和 gui 默认引入但 widgets 必须显式声明如果用 Qt 6 打开老工程漏了这一行会报一大堆找不到 QWidget 头文件的错误。res.qrc 是把图片资源编译进可执行文件的清单文件格式是 XMLRCC qresource prefix/ fileres/background.png/file fileres/run_1.png/file fileres/run_2.png/file fileres/run_3.png/file /qresource /RCC所有图片走 qrc 打包进二进制交付时不会出现“工程拷走了但图片丢了一半”的惨剧。代码里引用资源统一用:/前缀/文件名的写法比如上面这段就对应:/res/run_1.png。如果你要拿这个工程改成自己的作业第一步是把 TARGET 改成自己的项目名别用 running 直接交。包里的 README 还附了实机运行截图先看截图确认界面长啥样再对照代码会顺很多。3. 游戏循环与动画帧定时器、帧动画和碰撞判定跑酷游戏的核心说穿了就三件事循环刷新、角色动起来、判定撞没撞。Qt 里做游戏循环最土但最稳的方案是 QTimer 驱动不用多线程不用 QOpenGLWidget一个定时器加几个槽函数就能把帧循环跑起来。这个项目就是把 Qt 的这个特性用满了。3.1 QTimer 驱动的主循环为什么不用 while有人会习惯性在事件循环里写 while(1) { update(); }这是 Qt 新手最容易翻车的地方主线程卡死后窗口的事件分发会停止界面直接变成“假死”。这个项目用的是 QTimer 周期性触发槽函数让所有游戏更新都在事件循环的空闲窗口内执行窗口始终能响应键盘事件和重绘请求。// gamesence.cpp 构造函数 m_timer new QTimer(this); m_timer-setInterval(30); // 每 30ms 刷一帧约 33fps connect(m_timer, QTimer::timeout, this, GameSence::onTimeout); m_timer-start();间隔 30ms 是我推荐跑酷游戏的起步值。33fps 对于这种简单 2D 游戏完全够用而且 CPU 占用率不会太高。如果你把间隔调成 16ms约 60fps在低配机器上反而可能不稳定因为刷帧和事件循环抢占资源掉帧比 30ms 更明显。void GameSence::onTimeout() { for (Barrier* b : m_barriers) { b-moveLeft(speed); // 障碍物按当前速度左移 } for (Feibiao* f : m_feibiao) { f-moveRight(flySpeed); // 飞镖向右飞行 } m_character-updateFrame(); // 切换角色动画帧 checkCollision(); // 碰撞判定 update(); // 触发整体重绘 }每帧做的事非常直观先移动障碍物再移动飞镖然后角色换下一帧动画最后做一次碰撞检测。你注意看顺序——先移动再检测这样这一帧的碰撞结果是准确的如果先检测再移动碰撞会晚一帧体感上就是“明明没碰到却死了”。这是游戏循环里很典型的时序坑调整难度或者加新逻辑时保持这个先后顺序不要乱。3.2 奶龙帧动画setPixmap 换图与 QPixmap 缓存跑酷角色的动作本质就是多张静态图片循环播放。这个项目里的角色素材是一组奶龙图片帧实现方式是每次定时器触发时更换 QLabel 上的 pixmap。void Character::updateFrame() { m_frameIndex (m_frameIndex 1) % m_frameList.size(); QPixmap pix m_frameList.at(m_frameIndex); setPixmap(pix); adjustSize(); }m_frameList 是一个 QVector 在构造函数里一次性把所有帧加载进内存m_frameList.append(QPixmap(:/res/character_1.png)); m_frameList.append(QPixmap(:/res/character_2.png)); m_frameList.append(QPixmap(:/res/character_3.png));这里最值得学的习惯是“一次性加载每帧只换图”。QLabel 的 setPixmap 本身是高效的任务栏不会闪烁。网络上有人用 QMovie 播放帧序列但电影只在Qt载入Gif时有优势动画帧需要自己控制比如攻击帧、跳跃帧要和游戏事件绑定QMovie 做起来很别扭。所以课程设计框架还是用自绘帧比较省心。调整动画速度只需要改 updateFrame() 的调用频率也就是第 3.1 节里 QTimer 的事件间隔。间隔越小跑动频率越快角色看起来越急促。3.3 碰撞判定QRect::intersects 与飞镖判定碰撞判定在这个项目里不需要花哨算法QRect 的 intersects 就够。bool GameSence::hitTest(const QRect a, const QRect b) { return a.intersects(b); }角色、障碍物、飞镖本质上都是一张 QLabel 贴图它们都有自己的几何区域。在 checkCollision() 里逐步处理两种关系角色 vs 障碍物——碰到就游戏结束或扣命飞镖 vs 障碍物——碰到就两个一起消失这是“攻击可消除障碍物”的核心逻辑。void GameSence::checkCollision() { QRect roleRect m_character-geometry(); for (Barrier* b : m_barriers) { if (!b-isActive()) continue; if (hitTest(roleRect, b-geometry())) { gameOver(); // 角色撞上障碍 return; } for (Feibiao* f : m_feibiao) { if (hitTest(b-geometry(), f-geometry())) { b-setActive(false); f-setActive(false); } } } }参数上要注意geometry() 拿到的坐标是相对父窗体的如果角色和障碍物不在同一个父容器里坐标系不一致QRect 的 intersects 就会误判。常见做法是把这些实体全部挂在 gamesence 这个统一容器下保证坐标系一致。这也是我建议你改源码时不要轻易改动实体挂载点的原因一改容器碰撞全乱。飞镖的移动方向是向右的速度比障碍物左移更快所以它能“追上”障碍物。如果飞镖飞出了屏幕右边界按 m_active 标记回收防爆内存。整套逻辑不复杂但一个 QTimer 把刷新、动画、碰撞这三件事串起来对课程设计答辩来说已经是很完整的闭环了。4. 从源码到运行Qt 环境配置与完整编译流程如果你电脑上已经装好 Qt这一章可以跳着看。但如果你是新换的电脑、第一次拉课设代码或者被 Qt 的版本问题折腾过那下面这一套流程能省一晚上。这里讲的是这个源码包从打开到跑起来的完整链路。4.1 环境选择从 .pro 判断 Qt 版本兼容性先看 running.pro 用的模块这个工程没有碰高版本特性Qt 5.9 以上理论上都能编译。给课设选环境最稳的组合是 Qt 5.15.2 加 Qt Creator 自带的 MinGW 编译器。原因很直接5.15.2 是 Qt 5 系列最后期的大版本网上教程、博客、答疑帖基本都基于这个版本报错了也好搜我自己踩过 Qt 6 打开老工程时编译报错的坑核心问题大多出在 widgets 模块声明、废弃函数这两类课设阶段没必要跟这些较劲。如果你本机已经装了 Qt 6也不是不能用但要确认 running.pro 里的QT core gui后面有没有widgets。Qt 6 把 widgets 剥离得更彻底少了这一行编译会直接报一堆找不到 QWidget 的错。另一个要确认的是编译器套件Windows 下两种常见选择MinGW 和 MSVC。代码里没有用 MSVC 专属语法所以理论上两个都能编但建议统一别工程文件用 MinGW 编环境变量 PATH 里却先找到 MSVC 的 Qt 版本这会引出第 5 章那个著名的库冲突报错。4.2 在 Qt Creator 里构建与运行图形界面操作图形界面操作都发生在 Qt Creator 里打开 Qt Creator“文件”菜单选“打开文件或项目”定位到 running.pro。弹出构建套件选择页面勾选你安装好的 Qt 5.15.2 MinGW 套件。点左下角的“构建”锤子图标先 Debug 编译一遍。编译日志出现 “running.exe was created” 之类的输出后点绿色三角运行。这里有三点注意。第一工程路径和文件路径里不要有中文和空格玄学问题的高发区第二如果之前编译过老版本先执行一次“清理项目”再重新构建否则可能残留旧的 moc 文件导致行为诡异第三Qt Creator 打开 .pro 文件时会自动做一次 qmake 解析如果你看到弹出的窗口里多了个“Kit Selection”说明机器上有多个 Qt 版本选 5.15.2 那个就行。命令行编译的操作也给一下方便习惯终端的同学mkdir build cd build qmake ../running.pro mingw32-make # Windows MinGW 环境 # 或者 make # Linux/macOS ./running # 运行生成的可执行文件参数说明qmake 是生成 Makefile 的步骤mingw32-make / make 是真正调用编译器逐个编译源文件的步骤。命令行和 Qt Creator 的图形构建本质是一回事区别只在于 Creator 自动帮你选了套件、整理了路径。4.3 改参数速度、生成间隔、跳跃力度游戏手感不好问题基本都在下面这张参数表里这是排除逻辑时优先调的位置参数常见位置参考值影响QTimer 刷新间隔gamesence 构造函数30ms全局节奏越小越快障碍物左移步长gamesence::onTimeout3px/帧关卡难度核心飞镖右移步长gamesence::onTimeout5px/帧攻击手感障碍物生成间隔spawnBarrier() 里1200ms越密越难跳跃速度character::jump10px/次滞空高度与时长比如想做一个“开局温柔、后期刺激”的难度曲线核心是让 speed 和 spawnInterval 这两个值动态变化。常见做法是在每次生成新障碍物后把速度加 0.5、间隔减 50ms用两个成员变量存当前值而不是写死常量。这种修改代码量很小但答辩演示效果非常明显——老师能看到你确实理解了难度控制而不只是背代码。5. 避坑记录Qt 跑酷项目最常见的五个翻车现场下面这五条坑是我觉得这个项目复现概率最高、也最容易劝退初学者的每条按“现象 → 原因 → 解决”的顺序拆干净。5.1 编译通过运行崩溃Qt 库版本冲突现象编译不报错一运行就弹出错误内容含有fatal: cannot mix incompatible Qt library (version ex50601) with this library然后程序退出。原因机器上存在多个 Qt 版本exe 运行时加载到的 Qt5Core.dll 和编译时用的不是同一个。典型场景是之前装过 5.9后来又装了 5.15PATH 环境变量里两个 bin 目录都在动态库加载顺序错乱。解决Windows 下打开“系统属性 → 环境变量”把当前使用的 Qt 版本 bin 目录移到 PATH 最前面同时把其他 Qt 版本的 bin 删掉。然后在 Qt Creator 的“工具 → 选项 → Kits”里确认当前套件指向的 Qt 版本和运行目录一致。我自己的习惯是在 exe 同目录放一个 qt.conf 文件内容是[Paths] PrefixD:/Qt/5.15.2/mingw81_64这样 exe 就会优先从这个固定路径找 Qt 库不会再被系统 PATH 干扰。5.2 图片一片空白qrc 资源路径写错现象窗口和按钮都正常但角色和背景是空白或者 Qt Creator 提示找不到文件。原因res.qrc 里写的路径与实际文件位置不一致。常见错误是把文件加进了 qrc但代码里引用时前缀写错比如 qrc 里前缀是/代码写成:/image/run_1.png对不上。解决在 Qt Creator 左侧的资源编辑器里展开 qrc 文件确认每个资源文件前面是绿标说明文件真实存在然后看 qresource 的 prefix 属性是/还是/image代码引用必须完全一致。还有一个平时注意不到的坑往 qrc 里新增图片后有些版本不会自动刷新需要手动重新构建一次项目才能生效。5.3 方向键和空格没反应键盘事件被焦点吞掉现象开始界面能点按钮但进入游戏后按方向键、空格都没反应点一下窗口才恢复。原因键盘事件默认分发到当前拥有焦点的控件上。startscence 里的按钮被点击后焦点仍然停留在按钮上gamesence 窗口本身没有拿到焦点自然收不到键盘事件。另一种常见情况是 gamesence 没有设置焦点策略默认不接受键盘焦点。解决在 gamesence 构造函数里调用setFocusPolicy(Qt::StrongFocus)同时重写 showEvent在显示时强制抢焦点void GameSence::showEvent(QShowEvent *event) { QWidget::showEvent(event); setFocus(); }如果上面两步还不行可以下狠招——直接在 gamesence 里重写 event 函数从事件队列层面截获键盘事件bool GameSence::event(QEvent *ev) { if (ev-type() QEvent::KeyPress) { QKeyEvent *keyEv static_castQKeyEvent*(ev); if (keyEv-key() Qt::Key_Space) { m_character-jump(); return true; } } return QWidget::event(ev); }这是最稳的写法绕开了焦点问题。5.4 每次重开障碍物排列都一样随机数种子没设现象每次重新开始游戏障碍物出现的顺序和间隔几乎不变多玩几局就能背板。原因代码里用了 rand() 或者 qrand()但没有设置随机种子默认种子是固定的。很多 C 教材讲 rand() 时没强调 srand 这件事于是就成了课设里的经典玄学。解决在游戏初始化时设置种子。Qt 5 沿用的写法是#include QtGlobal #include QTime qsrand(QTime::currentTime().msec()); // 设置随机种子 int index qrand() % 3; // 生成 0~2 的随机障碍物类型注意 Qt 5.15 已把 qsrand 标为废弃Qt 6 直接移除了所以如果用 Qt 6直接用 C11 的随机数库更干净std::mt19937 rng(std::random_device{}()); std::uniform_int_distributionint dist(0, 2); int index dist(rng);别小看这个坑答辩演示时如果同一套排列出现三次老师很容易怀疑是写死的序列。5.5 动画闪烁或掉帧换图时机和重绘策略现象角色快速移动时画面闪烁像是有残影低配机器上更明显。原因setPixmap 每次会触发该控件区域的重绘如果背景和角色是分开的两个控件重绘区域没对齐就会出现闪一下的空档。另一个原因是定时器间隔太短刷帧和重绘打架。解决QLabel 默认是有双缓冲的一般不闪。真正闪的是那些继承 QWidget 后自己重写 paintEvent 画背景的项目。如果后续改造时你用了自绘背景写法上注意两点一是配合setAttribute(Qt::WA_OpaquePaintEvent)跳过无必要的背景重绘二是在 paintEvent 里把整块背景一次画完不要分多次 fillRect 叠加。调场景时发现局部闪烁优先检查是不是在 paintEvent 里做了逐像素计算这类自绘代码的坑远多于 QLabel 换图方案。6. 把课设打磨成作品三个低成本优化技巧6.1 加音效QSoundEffect 两行代码搞定跑酷游戏没有声音演示时存在感会弱一截。Qt 5 里配套最轻的是 QSoundEffect播放 wav 文件不需要额外引入多媒体模块#include QSoundEffect QSoundEffect *jumpSound new QSoundEffect(this); jumpSound-setSource(QUrl(qrc:/res/jump.wav)); jumpSound-setVolume(0.5f); jumpSound-play();注意 setSource 里的路径要对应 res.qrc 里的前缀格式文件格式尽量用 16bit 44.1kHz 的 wav兼容性最好。加分写法是跳之前调用 play()跳的瞬间刚好发声这行代码能直接提升演示观感。6.2 难度曲线把死参数改成动态区间第 4.3 节说过参数表优化成动态区间才是完整方案int speed 3 m_score / 500; int spawnMs 1200 - qMin(600, m_score / 300);每得 500 分速度加 1生成间隔以 300 分递减下限锁在 600ms。这个设计比均匀加速聪明的点是“阶梯式”加速明显可见答辩时能讲出来低分段新手友好高分段难度加速同时用 qMin 保证不会快到无法反应。6.3 演示录制与答辩自检课程设计演示时长最佳控制在两分钟以内打开开始界面 → 展示角色移动、跳跃、飞镖攻击 → 撞到障碍物触发结束 → 回答两个参数问题。录制建议用工具直接截成动图在答辩机上试播一遍不要在答辩现场才开始编译——现场编译出问题的概率比玄学还玄学。那之后我每次交课程设计都会强迫自己走一遍流程换一台干净电脑重新编译、从头到尾录一段不被中断的演示、强行改一个参数看运行结果变化。这三步做完整个项目基本就稳了。这份源码的类划分、QTimer 循环、碰撞判定方式都很适合直接拿去做骨架替换成自己的角色素材和主题就能变成新的作业。希望这份源码拆解能帮你顺利落地也祝你答辩顺利。本文还有配套的精品资源点击获取
返回列表