ARTICLE DETAIL

资讯详情

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

QT陨石飞机游戏开发实战:从C++课程设计到完整项目

QT陨石飞机游戏开发实战:从C++课程设计到完整项目 简介一份面向计算机专业学生的 C 期末大作业/毕业设计参考项目基于 Qt 框架实现陨石撞击飞机小游戏覆盖界面渲染、玩家控制、陨石生成、碰撞检测、计分与生命值、难度递增等完整流程。压缩包共 79 个文件、34.93MB主体为 8 个 C 源文件和 8 个头文件配合 47 张 PNG 界面元素/场景图、5 张 JPG 背景、WAV 音效、ICO 图标以及 .pro 工程文件、qrc/rcc 资源配置、ts 翻译文件和 README 说明目录结构清晰便于直接打开工程对照学习。项目源码均在本地编译调试通过并附带文档说明适合课程设计、期末作业或实战练习已有 59 人学习/下载。资源从需求分析、游戏设计到具体实现均有完整呈现可参考其资源分包、多关卡地图、玩家飞机与道具设计等模块化写法对想提升 Qt/C 项目能力的学习者有较高参考价值。1. 从期末大作业到完整游戏QT陨石飞机项目到底在做什么期末课程设计里C 大作业的经典题目非常多而基于 QT 的陨石撞击飞机游戏几乎年年都能在计算机、软件、物联网专业里见到。它不需要 3D 引擎不需要复杂算法只要一个窗口、两个定时器和一组碰撞判断就能把 C 课程里的类、容器、事件、绘图串成一个能现场演示的完整作品。这个项目真正考察的不是“算法多难”而是你对 QT 事件循环、绘图刷新和对象生命周期有没有直观理解。适合第一次接触 QT 的课程设计初学者也适合想快速把 GUI 项目跑起来的期末赶工人。玩法一句话就能说清键盘控制飞机左右上下移动陨石不断从屏幕顶部下落碰到即结束存活时间越长分数越高。要把成绩做成“看一眼就是完整作品”重点在三个地方——程序能不能稳定跑完演示、帧率顺不顺畅、状态切换时会不会闪退或崩溃。下面从选型到答辩把这条路线完整过一遍。2. 环境搭建与项目骨架Qt 5.15.2/C17 下跑通最小版本2.1 选型为什么我建议 Qt 5.15.2 MSVC 2019 64 位第一个决策点就是 Qt 版本。网上搜“qt 下载”“qt creator”能翻到 Qt 5.12、5.15、6.x 一堆版本。我一般建议期末项目锁在 Qt 5.15.2原因很实际它是 LTS 长期支持版本网上搜“qt 5.15.2 配置”“msvc2019_64”能找到大量历史教程你踩过的坑大概率已经有人贴过解决办法。Qt 6 虽然新但模块划分和部分 API 有调整评委老师如果用的是 Qt 5 环境你自己升到 6 反而容易在演示时翻车。编译器方面最稳妥的是跟随 Qt 安装包里的 MSVC 2019 64 位组件并把 Qt Creator 的构建套件Kit切到这一套。MSVC 和 MinGW 的差别在于前者编译出的程序换到另一台 Windows 机器上需要装有对应版本的 Visual C Redistributable 运行库后者是 MinGW 运行时。期末答辩大概率要换电脑演示统一用实验室或老师推荐的那套会省掉很多“为什么我这跑不起来”的尴尬。注意选 MSVC 套件时Creator 里显示的编译器名称要和 Qt 库目录里的msvc2019_64对应。装的是 2019 就用 2019装 2022 却选 2019 的库编出来的程序一样能跑但链接时偶尔会跳警告不建议逃课式组合。2.2 pro 文件四行配置决定你今晚几点睡.pro文件是 qmake 的工程描述。我帮人看项目时第一件事就是打开.pro百分之六七十的编译报错都能在这定位到原因。一个陨石飞机项目的最小.pro长这样QT core gui widgets CONFIG c17 TARGET MeteorGame TEMPLATE app SOURCES main.cpp gamewindow.cpp HEADERS gamewindow.h逻辑说明第一行的widgets是 QWidget、QPainter、QRectF 这些类所在的模块。core和gui是 Qt 自带依赖实际使用中QT widgets会自动带上它们写全是为了让人看清引了哪几个模块。CONFIG c17让编译器按 C17 标准编译QList、共享指针、结构化绑定这些特性都能用。TARGET是生成的 exe 名字TEMPLATE app表示这是一个可执行程序。参数说明如果你之后加了资源文件比如图标、图片、音效还要补RESOURCES res.qrc。如果图片用到了QPixmap但.pro忘了写widgets编译时不会立刻报错等到链接阶段才会以“无法解析的外部符号”形式出现新人很容易被这个误导。所以项目创建后第一件事是确认.pro里这几行都没少。2.3 最小骨架让一个矩形先动起来不要一上来就写飞机、陨石、分数先把“窗口能显示、定时器能驱动、键盘能响应”这条最小链路跑通。我用一个矩形代替飞机方向键控制它移动这样能用最少代码验证 QT 的核心机制。#include QApplication #include QWidget #include QTimer #include QKeyEvent #include QPainter class TestWindow : public QWidget { public: TestWindow() { setFixedSize(800, 600); setFocusPolicy(Qt::StrongFocus); QTimer* timer new QTimer(this); connect(timer, QTimer::timeout, this, [] { if (leftPressed) posX - 4; if (rightPressed) posX 4; update(); }); timer-start(16); } protected: void keyPressEvent(QKeyEvent* e) override { if (e-key() Qt::Key_Left) leftPressed true; if (e-key() Qt::Key_Right) rightPressed true; } void keyReleaseEvent(QKeyEvent* e) override { if (e-key() Qt::Key_Left) leftPressed false; if (e-key() Qt::Key_Right) rightPressed false; } private: void paintEvent(QPaintEvent*) override { QPainter painter(this); painter.fillRect(posX, 500, 40, 30, Qt::blue); } bool leftPressed false; bool rightPressed false; int posX 400; }; int main(int argc, char* argv[]) { QApplication app(argc, argv); TestWindow w; w.show(); return app.exec(); }逻辑说明setFocusPolicy(Qt::StrongFocus)让窗口能收到键盘事件很多初学者漏掉这句按键怎么按都没反应。定时器每 16 毫秒触发一次 lambdalambda 里根据按键状态更新posX再调用update()请求重绘。paintEvent里只做一件事——把当前状态的画面画出来不改变任何游戏数据。参数说明16毫秒约等于 60 FPS。这里用的是 bool 记录按键状态而不是在 keyPressEvent 里直接移动原因是按住方向键时系统会先触发一次 keyPress再按固定频率触发 keyRelease 和 keyPress 的自动重复消息如果在事件里移动移动速度会忽快忽慢而且无法同时响应多个按键。用状态记录是游戏键盘输入的常见做法后续加 WASD 也只是多两个 bool 的事。3. 核心玩法实现陨石生成、飞机移动与碰撞检测3.1 对象建模Plane 类管移动Meteor 类管坠落游戏逻辑稍微复杂后再全写在窗口类里就会变成“上帝类”。一个窗口类几百行、又管键盘又管绘制又管碰撞期末答辩老师问“这个功能在哪个类”你只能尴尬地指向一个大函数。常见的做法是给飞机和陨石各建一个普通 C 类它们不继承 QWidget只保存数据并提供一个绘制方法。// plane.h #ifndef PLANE_H #define PLANE_H #include QPointF #include QRectF #include QPainter class Plane { public: void setPosition(qreal x, qreal y) { pos QPointF(x, y); } void moveBy(qreal dx, qreal dy) { pos QPointF(dx, dy); } // 返回飞机所在的碰撞矩形 QRectF rect() const { return QRectF(pos.x() - 20, pos.y() - 15, 40, 30); } void paint(QPainter* painter) const { painter-save(); painter-setBrush(Qt::cyan); painter-setPen(Qt::NoPen); painter-drawRect(rect()); painter-restore(); } private: QPointF pos; }; #endif // PLANE_H// meteor.h #ifndef METEOR_H #define METEOR_H #include QRectF #include QPainter #include QRandomGenerator class Meteor { public: static Meteor randomInside(int screenWidth) { Meteor m; m.pos.setX(QRandomGenerator::global()-bounded(screenWidth)); m.pos.setY(-m.r * 2); m.speed QRandomGenerator::global()-bounded(2, 6); return m; } void fall() { pos.ry() speed; } QRectF rect() const { return QRectF(pos.x() - r, pos.y() - r, 2 * r, 2 * r); } bool isOutOfScreen(int screenHeight) const { return pos.y() - r screenHeight; } void paint(QPainter* painter) const { painter-save(); painter-setBrush(Qt::darkGray); painter-setPen(Qt::NoPen); painter-drawEllipse(rect()); painter-restore(); } private: QPointF pos; qreal r 18; qreal speed 3; }; #endif // METEOR_H逻辑说明两个类都不依赖窗口Plane通过moveBy改变坐标Meteor通过fall让 y 坐标每帧增加。rect()是碰撞和绘制共用的数据源飞机用 40x30 的矩形近似陨石用正方形外框圈出圆QRectF::intersects判断的是这两个框是否相交。参数说明randomInside用QRandomGenerator::global()-bounded生成随机数第一个参数是陨石 x 范围第二个是下落速度范围 2~6。把生成函数设计成静态工厂而不是构造函数里随机是为了让代码更明确——你在窗口里调用Meteor::randomInside(width())时一眼就知道这是在创建一颗随机陨石。qreal在大多数平台上是 double不需要纠结。3.2 游戏循环QTimer 的 16 毫秒与 paintEvent 的配合游戏循环的核心槽函数在窗口类里每一帧做的事只有四件读按键、更新飞机位置、更新所有陨石位置、检查碰撞。检查完再调用update()由 QT 在合适时机触发paintEvent。void GameWindow::updateFrame() { if (!running) return; // 1. 读取当前按下的方向键每帧移动 4 像素 if (pressedKeys.contains(Qt::Key_Left) || pressedKeys.contains(Qt::Key_A)) plane.moveBy(-4, 0); if (pressedKeys.contains(Qt::Key_Right) || pressedKeys.contains(Qt::Key_D)) plane.moveBy(4, 0); if (pressedKeys.contains(Qt::Key_Up) || pressedKeys.contains(Qt::Key_W)) plane.moveBy(0, -4); if (pressedKeys.contains(Qt::Key_Down) || pressedKeys.contains(Qt::Key_S)) plane.moveBy(0, 4); // 2. 所有陨石向下落 for (Meteor m : meteors) { m.fall(); } // 3. 自后向前遍历出界则移除碰撞则结束游戏 for (int i meteors.size() - 1; i 0; --i) { if (meteors[i].isOutOfScreen(height())) { meteors.removeAt(i); continue; } if (plane.rect().intersects(meteors[i].rect())) { running false; frameTimer-stop(); spawnTimer-stop(); update(); return; } } update(); }逻辑说明pressedKeys是QSetint记录当前所有处于按下状态的键名。这样设计的好处是同时按“右”和“上”时飞机能斜着走而不是互斥。陨石遍历时从size() - 1向前删避免删除中间元素导致后面的下标错位——这是 QList 遍历删除最容易踩的坑。碰撞命中后停掉两个定时器界面停留在最后一帧文字提示等状态管理放下一章做。参数说明移动量 4 像素对应 16ms 帧间隔理论上每秒 250 像素。如果你把定时器改成 8ms 或陨石速度调大移动量和碰撞体积也要联动调整。这里更工程的做法是基于物理时间计算位移我在第 5 章的卡顿问题里会细讲。3.3 碰撞检测矩形相交与圆心距离怎么选前面代码里用的是矩形相交这是最简单也最稳定的方式QRectF::intersects是 Qt 内置方法运算开销小。但陨石是圆飞机的矩形四角在视觉上并没有物体用外接圆做检测在某些角度下判定更符合直觉bool hitByCircle(const QPointF center1, qreal r1, const QPointF center2, qreal r2) { qreal dx center1.x() - center2.x(); qreal dy center1.y() - center2.y(); qreal minDist r1 r2; return dx * dx dy * dy minDist * minDist; }逻辑说明判断两圆是否相交只需比较圆心距离和半径和。代码里用平方和比较省掉了一次sqrt开方运算碰撞检测每帧要做几十次这个优化虽然渺小但写法更干净。圆的圆心可以从rect().center()拿到半径就是rect().width() / 2。参数说明矩形检测适合“飞机”这种大体接近矩形的物体圆形检测适合陨石。期末项目完全可以把两个方法都写出来飞机用矩形、陨石用圆然后组合判断先判断飞机矩形是否包含陨石圆心再判断圆心到矩形四条边的最短距离是否小于半径。这个组合法在答辩时能多讲一分钟属于不会扣分还能加分的部分。不要做的是每帧创建一堆 QRectF 临时对象然后用指针管理让代码绕得看不懂。4. 画面表现与游戏状态从“能跑”到“像个游戏”4.1 QPainter 绘图抗锯齿、贴图和视觉层次paintEvent是整个游戏唯一允许出现 QPainter 绘图的地方。很多人把游戏逻辑写在里面然后发现程序忽快忽慢——因为重绘频率不固定逻辑被重绘次数绑架了。绘图函数里只做三件事按状态画背景、画所有游戏对象、画 UI 文字。void GameWindow::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); // 画背景上半部分深蓝渐变模拟星空 QLinearGradient bg(0, 0, 0, height()); bg.setColorAt(0, QColor(10, 10, 30)); bg.setColorAt(1, QColor(30, 30, 60)); painter.fillRect(rect(), bg); // 画飞机贴图版 painter.setRenderHint(QPainter::SmoothPixmapTransform); painter.drawPixmap( QPointF(plane.rect().left(), plane.rect().top()), planePixmap); // 画所有陨石 for (const Meteor m : meteors) { m.paint(painter); } // 画分数 painter.setPen(Qt::white); painter.setFont(QFont(Microsoft YaHei, 14)); painter.drawText(20, 40, QString(分数: %1).arg(score)); }逻辑说明setRenderHint(QPainter::Antialiasing)让圆形和斜线边缘平滑否则陨石会呈锯齿状。QLinearGradient用两点颜色做一个星空渐变比纯色背景更有“游戏感”。drawPixmap之前要把图片加载成成员变量planePixmap不要在绘图函数里重复QPixmap::load磁盘 IO 会让帧率断崖式下跌。参数说明飞机贴图尺寸最好预缩放到 40x30 左右和碰撞矩形保持一致否则会出现“图片被陨石碰到了但判定没触发”或反之的违和感。drawText的坐标是文字左上角QPainter 的坐标原点在屏幕左上角、y 轴向下很多习惯数学坐标系的人会把文字画出屏幕外这也是常见的低级坑。4.2 状态机开始、暂停、结束三种界面怎么切换游戏逻辑逐渐丰富了最迟在这里必须引入状态。常见做法是用一个枚举变量GameState保存当前状态主循环每帧先看状态再决策只有Running状态才更新逻辑。enum class GameState { Ready, Running, Paused, GameOver }; void GameWindow::updateFrame() { if (state ! GameState::Running) return; // ...移动、下落、碰撞检测逻辑... if (hit) { state GameState::GameOver; frameTimer-stop(); spawnTimer-stop(); update(); } } void GameWindow::keyPressEvent(QKeyEvent* e) { if (e-key() Qt::Key_Space) { if (state GameState::Running) { state GameState::Paused; frameTimer-stop(); } else if (state GameState::Paused) { state GameState::Running; frameTimer-start(); } else if (state GameState::GameOver) { resetGame(); } update(); return; } pressedKeys.insert(e-key()); } void GameWindow::paintEvent(QPaintEvent*) { // ...绘图... if (state GameState::Ready) { painter.drawText(center, 按空格开始); } else if (state GameState::Paused) { painter.drawText(center, 游戏暂停按空格继续); } else if (state GameState::GameOver) { painter.drawText(center, QString(游戏结束得分 %1按空格重开).arg(score)); } }逻辑说明状态枚举放在GameWindow的头部或者单独的头文件里。Ready时定时器不启动画面只画静态的初始场景Running时主循环全速运行Paused停掉定时器但保留画面避免重绘数据不一致GameOver后空格键调用resetGame()清空陨石、归零分数重新进入Ready。这样设计后每个函数入口都先看状态状态越多越不会互相干扰。提示QTimer::stop()不会销毁定时器它只是不再触发 timeout。可以反复 start 和 stop比每次 new 一个 QTimer 安全得多。4.3 手感细节键位映射、陨石密度和音效陨石生成逻辑我在第 3 章提到用spawnTimer定时生成间隔 800ms。实际玩起来你会发现 800ms 太均匀几分钟后节奏就着摸透了。常见调整是让生成间隔随时间递减比如每 30 秒把间隔从 800 降到 500难度曲线就出来了spawnTimer-setInterval(qMax(400, 800 - playSeconds * 10));参数说明qMax保证间隔最短不会超过 400ms否则满屏陨石直接没法玩。下落速度也可以从 2~6 均匀分布调整为随游戏时间推进整体上调这两处参数配合评委现场试玩时会明显感到“有难度设计”。音效用QSoundEffect播放 wav 文件生命周期要作为成员变量局部变量一结束声音马上就没了。音效是典型的加分项但还是那句——先保证碰撞和状态稳定最后一天有空再顺手加。画面再好看卡顿或崩溃都是前功尽弃。5. 期末大作业避坑指南编译报错与逻辑翻车现场5.1 编译期报错dependent ......\qt\5.15.2\msvc2019_64\include\qtwidgets现象项目一开始还正常某次加了图片资源后Qt Creator 编译输出栏突然冒出一行红色报错类似:-1: error: dependent ..\..\..\..\..\..\qt\5.15.2\msvc2019_64\include\qtwidgets does not exist.后面还跟着一堆找不到头文件的提示。原因这个报错翻译成人话是“Qt 找不到 widgets 这个头文件目录”。最常见的触发条件有三个.pro里少了QT widgets项目目录路径太长Windows 下超过 260 字符路径上限换过 Qt 安装位置但打开的是旧的.pro.user文件构建套件还指向旧路径。解决第一步打开.pro确认QT core gui widgets这一行存在。第二步项目菜单里选“清理项目”删掉 build 目录重新 qmake 并构建。第三步如果还是报错把整个项目目录剪切到盘符根目录比如D:\MeteorGame路径从 200 多字符缩到 30 个字符内。多数依赖路径问题走到这一步就解决了。# 命令行方式重建项目比在 Creator 里把 build 文件夹右键删除更彻底 cd /d D:\MeteorGame D:\Qt\5.15.2\msvc2019_64\bin\qmake.exe MeteorGame.pro nmake clean nmake参数说明上面假设你的 Qt 装在D:\Qt编译器用的是 MSVC 对应的 nmake。用命令行构建的好处是能看到完整错误流不会像 Creator 里只截一行。如果你用的是 MinGW 套件最后一行要换成mingw32-make别照抄。5.2 中文乱码MSVC 把源码当作本地编码读现象窗口标题、按钮文字、注释在别人电脑上打开显示成???或乱码但在你自己机器上一切正常。原因Qt Creator 默认把源文件存成 UTF-8 编码MSVC 编译时如果源文件没有 BOM 头会按系统本地代码页 GBK 去读中文字符就被解释成别的字符了。解决.pro文件里加一行编译器选项告诉 MSVC 把源文件按 UTF-8 处理msvc { QMAKE_CXXFLAGS /utf-8 }逻辑说明/utf-8会让 MSVC 同时把源文件读取和执行字符集设为 UTF-8是中文 Qt 项目最省心的解法。如果你不想改.pro备选方案是每次新建文件后先用 Qt Creator 右下角的编码选择器把文件转成 UTF-8 with BOM这样 MSVC 也能识别。但 BOM 在部分 Git 和评审工具里会产生不可见字符统一用/utf-8更干净。5.3 碰撞漏检陨石穿过飞机的黑匣子现象陨石下落速度调到 8 以上肉眼清晰看到它从飞机身上“穿”了过去但碰撞判定就是没触发。原因现在是一帧检测一次碰撞。假设飞机只有 30 像素高陨石一帧下落 10 像素飞机顶部到陨石底部的相对位移如果一帧内超过了飞机高度上一帧和下一帧的采样点都会错过交集表现为穿模。解决给快速物体做“扫掠矩形”检测。把陨石上一帧位置和当前帧位置连成一个更大的矩形再用这个大矩形和飞机判断相交QRectF prevRect meteor.rect(); // 上一帧的位置 meteor.fall(); // 移动 QRectF curRect meteor.rect(); // 当前帧位置 QRectF swept prevRect.united(curRect); // 扫掠矩形 if (swept.intersects(plane.rect())) { // 判定碰撞 }逻辑说明united返回同时包含两个矩形的最小矩形物理意义上等价于陨石在这两帧之间划过的路径范围。这个矩形比原来的大得多所以能抓住高速穿过的漏检。参数说明扫掠矩形只对“移动的物体”有意义飞机移动时也可以对飞机做同样的处理。代价是碰撞判定会略微偏向保守比如陨石擦着边沿快速掠过时会提前几毫秒触发碰撞。游戏里这是可接受的误差而且会让玩家觉得“判定很严”不会骂穿模。这是黑匣子式玄学问题尽早用扫掠矩形能少掉不少头发。5.4 定时器与卡顿QTimer 不是银弹现象游戏刚打开很流畅拖动窗口、最小化再恢复后飞机移动明显变快或画面卡顿陨石位置像是“瞬移”。原因QTimer的 timeout 事件进入事件循环后如果主线程被窗口拖动等操作阻塞timeout 会被延迟触发。看起来表现为一帧耗时变成 200ms但游戏逻辑仍然按“每帧移动 4 像素”来算恢复后时间被吞掉物体位置比应该的位置偏小看起来变慢或者事件堆满后连续触发好几帧看起来像瞬移。解决不要假设每帧耗时都是 16ms用QElapsedTimer计算真实时间差让位移和速度挂到时间上QElapsedTimer deltaTimer; deltaTimer.start(); // 在 updateFrame 中 qint64 elapsed deltaTimer.elapsed(); qreal dt (elapsed - lastFrameTime) / 1000.0; // 秒 lastFrameTime elapsed; plane.moveBy(planeSpeed * dt * direction, 0);参数说明planeSpeed单位现在是“像素/秒”比如 240dt单位是秒一帧 16ms 时约 0.016。把dt乘进移动量里程序慢就少移快就多移物理时间保持一致。期末报告里写“使用基于帧时间差的运动模型”这行字比你在答辩现场说一百句“我优化过”都有说服力。不过要注意dt单帧上限比如超过 0.05 秒就当作 0.05否则从后台切回来那一下会一次性把几百像素的距离移完玩家直接懵。5.5 内存与崩溃new 出来的对象没人回收现象游戏运行几分钟后内存持续上涨关闭窗口时偶尔报段错误或者 Qt 输出 “QObject: Do not delete grandchildren”。原因多半出在new上。常见翻车现场是这样每次生成陨石就Meteor* m new Meteor(); meteors.append(m);游戏结束时只clear()列表——但这是 QList 清空元素不会 delete 指针堆上的 Meteor 对象一个都没释放。这还只是泄漏如果某个地方提前delete m后列表里还留着悬空指针碰撞检测一访问就直接崩溃。解决普通 C 对象直接用QListMeteor存值拷贝别用指针堆分配。如果陨石需要多态或有复杂的构造开销用QListstd::shared_ptrMeteor。所有 QObject 子类在构造函数里都要传入 parent例如QTimer* timer new QTimer(this)这样父窗口析构时它们会自动释放。// 遍历删除时自后向前避免下标错位 for (int i meteors.size() - 1; i 0; --i) { if (meteors[i].isOutOfScreen(height())) { meteors.removeAt(i); } }逻辑说明这条规则一句话总结——谁创建谁负责捞到篮子里的不一定都是菜。期末项目一般循环几千帧、生成上千颗陨石不用智能指针的话就算不崩溃内存曲线也是一个让人尴尬的上扬直线。运行完看任务管理器内存还留在高位答辩时被问到你总不能说是陨石太重。6. 让代码拿高分的进阶技巧模块划分、文档写作与答辩准备6.1 把游戏逻辑和界面绘制拆开MVC 最小落地如果你还有半天时间把GameWindow里的陨石列表、分数、碰撞判定全部搬到一个名叫GameModel的普通 C 类里。GameWindow只做三件事收集键盘输入、驱动model.updateFrame(dt)、在 paintEvent 里按model的数据绘制。这个 MVC 思路能让代码结构立刻从“大作业”变成“小项目”。答辩时老师问“你这个架构怎么设计的”你可以指着GameModel说“核心逻辑不依赖 Qt Widgets换到命令行或者服务器上也能跑”——这句话在期末答辩里就是一句话的“架构亮点”。6.2 文档说明需求分析、类图、测试三件套源码和文档说明放在同一个目录通常是一片几百字的“课程设计报告”结构遵循学校模板就行。但有几处建议别省需求分析里写玩法规则和功能列表总体设计画一张类关系图详细设计里把碰撞检测、定时器驱动这两个函数贴出来讲。截图要真实放“运行中的游戏画面”和“碰撞生效瞬间”各一张不要用网图。文档里代码和实际源码不一致是答辩时被扣分最常见的原因。6.3 答辩追问三个必背问题和参考答法第一个为什么用 QTimer 而不用线程答游戏逻辑简单主循环用定时器驱动足够线程还要考虑跨线程访问界面复杂度不值当。第二个碰撞检测矩形和圆形哪个更准答圆周用圆心距离更符合视觉矩形运算开销小工程上按物体形状混用。第三个游戏对象为什么不用 new答容器直接存储对象能自动管理内存避免手动 delete 遗漏也避免指针悬空。期末项目里我见过最多的遗憾不是算法不会写而是窗口类里堆了所有逻辑一演示就卡一关闭就崩。你花两小时把状态机和对象生命周期理清楚剩下的时候就会非常稳。这套路子我带人走了一遍又一遍最后说一句把文档截图留够把代码目录整理干净愿你的期末答辩不再翻车希望帮到你。本文还有配套的精品资源点击获取
返回列表