ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Qt/C++贪吃蛇开发实战:从环境搭建到游戏发布全流程

2026/10/7 22:00:05 拓冰建站 浏览量
Qt/C++贪吃蛇开发实战:从环境搭建到游戏发布全流程 做Qt/C开发也有年头了但每次有人问我能不能用Qt写个小游戏练手我第一个反应还是推荐贪吃蛇。不是因为简单而是这个项目麻雀虽小五脏俱全游戏循环、碰撞检测、键盘输入、界面刷新、状态管理全都能碰到做完之后你对Qt的事件循环和绘图机制理解会上一个台阶。尤其用C写跟那些拖拽控件就能出画面的脚本语言完全是两码事你得亲手管理数据结构、处理状态流转踩过坑之后才明白那些成熟的游戏框架替你扛了多少事。这篇文章就用一个完整的贪吃蛇项目把从环境搭建到最终发布的全过程拆开揉碎讲清楚。不论你是刚接触Qt想找项目练手的初学者还是被各种环境报错折腾到头秃的挣扎型选手又或者想看看别人怎么组织游戏循环逻辑的进阶玩家这篇文章都能给你一些实际的参考。1. 先搞定开发环境别让配置问题耗光你的热情写Qt程序有个潜规则真正的编码时间只占三分之一剩下三分之二全在跟环境较劲。我自己就经历过抱着今天一定把贪吃蛇跑起来的决心结果两个小时后还在跟MSVC版本和运行库较劲。所以第一步必须先花心思把环境弄利索这步省时间的收益远大于省事的收益。1.1 Qt版本和编译器怎么选选版本这事我的建议是别追新也别太恋旧。Qt 5.15.2算是一个非常经典的稳定版本支援到MSVC 2019的编译套件网上能搜到的解决方案也最多。很多人一开始想挑战最新版的Qt 6但6.x在模块划分和某些API上有调整新手踩到坑之后搜资料会发现很多答案还是基于Qt 5写的来回换算很折磨人。编译器这边尤其要注意你用MSVC 2019还是MinGW不是随便挑的。我强烈建议Windows上做开发直接用MSVC因为后续需要接第三方库时兼容性更好Qt官方二进制包基本都是按MSVC发布的调试工具链完整VS的调试器比GDB在某些场景下好用太多不过用MSVC意味着你的电脑上得装有Microsoft Visual C 2015-2022 Redistributable (x64)这个是运行时的前提。很多人明明Qt装好了一运行程序却报一堆DLL找不到十有八九就是这个运行库没装上。1.2 五步搭建Qt开发环境具体步骤我按实际踩坑后的实践顺序整理一下跟着走基本不会白折腾下载Qt安装器。去Qt官方渠道或者国内的清华镜像站下载在线安装器都行镜像站下载更快选自己合适的网络环境即可。安装时在组件选择界面重点勾选自己需要的MSVC套件版本。如果你用VS 2019就选msvc2019_64。组件不要贪多只选Qt 5.15.2里的MSVC 2019 64-bit和Qt Creator其余工具链和源码看需求再补全选的话下载量会非常感人。装完Qt Creator后在工具选项里检查编译器路径确认Kit里自动检测到MSVC和Qt版本看到绿色对勾才算绑定成功。写一个最小的QWidget程序跑一遍能弹出空白窗口就说明环境通过。如果你的主IDE是VS Code那还需要单独做VSCode配置C/C环境安装C/C插件、配置c_cpp_properties.json里的includePath指向Qt的include目录、配置tasks.json调用qmake或者CMake做构建。这条路可行但我建议第一次上手别折腾VSCode用Qt Creator少遭一半罪。1.3 新手最容易踩的三个环境坑我见过太多人卡在环境这一步三个高频坑给你提前打好预防针运行库缺失。程序编译通过一运行就报找不到Qt5Core.dll。这多半是环境变量没配或运行库缺失。解决方式要么把Qt的bin目录手动加进系统PATH要么用windeployqt工具打包依赖后者是正规做法后面发布部分会细讲。工具集版本不匹配。VS 2019装好了Qt Creator里却找不到对应的Kit或者一编译就报MSB8020之类的错误基本是VS的C工具集没安装完整。回到Visual Studio Installer里勾选使用C的桌面开发工作负载补装后再回来刷新Kit。路径里带中文和空格。Qt的构建系统对包含中文和空格的路径虽然比以前宽容了但依然存在各种奇怪的编译问题。安装路径和项目路径我都用纯英文别用游戏项目这种文件夹名当路径省得后面报错抛出来的路径乱码看得人脑壳疼。提示如果是Qt和Visual Studio联调报错先去Qt Creator的工具-选项-Kits-编译器里看看编译器类型对不对。出现黄色感叹号说明识别异常重新指定vcvars64.bat的路径再重启一次基本能解决。2. 开始之前先把游戏架构想透很多人上手写贪吃蛇第一反应是写个类把所有逻辑往一个文件里一塞然后启动一个while循环不停地刷新。跑起来确实能玩但过了两天想加个暂停功能想调一下速度曲线改起来会想骂人。贪吃蛇虽然小可它同样具备输入-逻辑-渲染三个核心环节如果一开始就用合理的结构组织代码后面的扩展性完全不同。2.1 贪吃蛇的三个核心环节与分离思路我把整个游戏拆成三层输入层接收用户的按键操作把方向变化转成游戏能理解的消息逻辑层管理蛇身位置、移动规则、食物生成、碰撞检测、得分计算渲染层把逻辑层的数据画到屏幕上这三层不是互相调用那么粗暴而是逻辑层作为核心输入层只负责改逻辑层的参数渲染层只负责读逻辑层的数据来绘制。这样设计的好处很多你想把Qt框架换成别的界面库逻辑层可以原封不动地搬走你想给游戏加上AI自动控制只需要替换输入层你想做单元测试甚至可以不去渲染直接调用逻辑层跑一轮游戏看结果。这种思路其实就是MVC或者说逻辑与视图分离的简化版。网上经常有人提Qt MVVM框架那是针对复杂桌面应用的贪吃蛇这种规模不需要上那么重的模式但核心的分离思想完全一致。包括有人问Qt拉动表格大数据卡顿时为什么推荐QTableView加自定义Model而不是TableWidget硬怼本质也是同一个道理数据和视图分离Scalable才能上来。2.2 蛇身体的数据结构选型这个环节很多人会忽视但恰恰是体现你对C理解的地方。蛇的本质是一个长度动态变化的序列每走一步头加一格尾去掉一格吃到食物时尾不缩。动起来之后头部不断变换坐标。针对这个场景我对比过几种方案数据结构操作便利度性能适用性QVector支持索引访问尾巴和头部中间插入删除慢但这里主要在两头可以接受QList双向操作有额外内存开销一般QQueue基于QList/QVector天然支持先进先出适合入队新头和出队旧尾但取倒数第二格麻烦不太顺手链表手写头尾操作灵活节点指针管理容易出错适合练习但没必要我最终用QVector存蛇的坐标点因为贪吃蛇的移动逻辑本质上不需要随机访问所有位置渲染但碰撞检测时通常要检查新头会不会跟某个身体节点重合这需要遍历全蛇身QVector内存连续遍历起来比QList缓存友好些。注意这里关键点不在于哪个结构最好而在于你对每种操作的成本有感知。用QVector在尾部push_back新头、头部removeFirst移除旧尾虽然removeFirst在QVector里是O(n)但贪吃蛇的长度通常不会几百格性能完全不是问题。如果真追求极致的O(1)手写环形缓冲区也能做到但为贪吃蛇做这个优化属于用大炮打蚊子。2.3 游戏循环用QTimer而不是while(1)刚接触Qt的人最容易犯的错误是写这样的逻辑while (true) { updateGameState(); repaint(); Sleep(100); }这个写法在GUI程序里是致命的你直接把UI线程死锁了窗口事件循环跑不起来界面会假死。哪怕你在循环里调用processEvents()也会引发各种重入问题快捷键按下去了界面却不刷新。Qt的正道是QTimer驱动的游戏循环。核心思路是由定时器按固定间隔发出timeout信号在这个信号槽里执行游戏逻辑和刷新。因为slot执行完就会把控制权还给Qt的事件循环界面能正常处理点击、按键、重绘一切都是顺滑的。关于QTimer的间隔和游戏速度的关系可以用一个简单的换算公式游戏速度定义为一秒内蛇移动的格数比如初始速度是每秒5格。那么定时器间隔 1000 / 速度单位毫秒。如果你想做加速效果就在每次吃到食物后把间隔减小但注意给个下限比如最小间隔80毫秒否则蛇快到人眼都看不清游戏就没法玩了。2.4 碰撞判定设计贪吃蛇最核心的规则就是三种碰撞逻辑不复杂但边界要仔细撞边界蛇头的坐标跑出游戏区域游戏结束撞自身蛇头的新坐标与蛇身任意一节重叠游戏结束吃食物蛇头的新坐标恰好与食物坐标重合蛇身加长刷出新食物这里有个容易写错的点要区分新头部和旧蛇身的关系。移动逻辑里新头部是当前头部加上方向向量尾部的移除是同步发生的但绘制时如果不把新头和旧尾的先后处理好会出现一帧里蛇头还没到新位置、蛇尾却被提前删掉半截的诡异视觉问题。我的做法是定义一个移动状态机先计算新头再做碰撞检测根据检测结果决定只加头不减尾吃到食物还是加头减尾正常移动最后才交给渲染。这一步时序问题处理得好后面调试会省很多精力。3. 核心代码实现一步步把蛇跑起来架构清楚了代码就是一砖一瓦的活。我建议用一个GameWidget类继承QWidget作为游戏画布所有游戏逻辑和绘制都放在这个类里主窗口只负责承载它并显示分数等信息。听起来拆得不细但已经足够清晰比把所有东西塞MainWindow里可维护性强多了。3.1 基础数据结构用结构体存坐标是所有实现的地基。需要注意使用qlonglong还是int窗口尺寸有限int完全够用别过度设计。struct SnakeNode { int x; int y; };游戏区域我用一个逻辑网格表示。假设游戏区域是20行x20列每格像素大小是CELL_SIZE比如30像素。那么逻辑坐标映射到像素坐标就是QRect cellRect(int gridX, int gridY) { return QRect(gridX * CELL_SIZE, gridY * CELL_SIZE, CELL_SIZE, CELL_SIZE); }网格化的好处是碰撞检测只需要比较整数坐标不用处理浮点精度问题渲染时也方便对齐。整个游戏状态我封装成一个枚举enum class GameState { Ready, // 准备开始 Running, // 游戏进行中 Paused, // 暂停 GameOver // 结束 };用状态机管理游戏生命周期很多莫名其妙的bug都出在状态切换上。比如暂停时还允许方向键改变方向、游戏结束时还继续生成食物有了状态机的门卫逻辑就清晰多了。3.2 蛇的移动与键盘响应每走一步的逻辑是这样void moveSnake() { SnakeNode newHead snake.first(); switch (currentDirection) { case Direction::Up: newHead.y - 1; break; case Direction::Down: newHead.y 1; break; case Direction::Left: newHead.x - 1; break; case Direction::Right: newHead.x 1; break; } handleCollision(newHead); if (gameOver) return; snake.insert(0, newHead); if (isFoodEaten) { score 10; generateFood(); } else { snake.removeLast(); } update(); }这里有几个细节值得注意。首先方向变量不能直接由键盘事件修改而是要经过一层校验。最经典的bug是蛇头向左的时候你按了右蛇撞自己死亡。解决方式很简单如果是当前方向的直接反向直接忽略。有些实现还会维护一个方向队列允许玩家在快速连按时连续转向。这个功能看个人需求但基础版我只建议做一个下一次方向变量避免连按导致一帧内蛇头连续拐两次而穿脖子。键盘事件上要重写keyPressEvent。我碰到过一种情况键盘方向键按下后蛇没有反应排查了半天发现是焦点在按钮上而不是游戏画布上。解决方式一是把所有控件设为setFocusPolicy(Qt::NoFocus)二是确保游戏画布拿到焦点或者在主窗口级别拦截按键事件。这里我建议后者主窗口作为最终事件处理器统一转发给游戏逻辑层后续加任意键开始这类功能也方便。void GameWidget::keyPressEvent(QKeyEvent *event) { if (state GameState::Ready) { startGame(); return; } if (event-key() Qt::Key_Space) { togglePause(); return; } if (state ! GameState::Running) return; switch (event-key()) { case Qt::Key_Up: if (currentDirection ! Direction::Down) pendingDirection Direction::Up; break; case Qt::Key_Down: if (currentDirection ! Direction::Up) pendingDirection Direction::Down; break; case Qt::Key_Left: if (currentDirection ! Direction::Right) pendingDirection Direction::Left; break; case Qt::Key_Right: if (currentDirection ! Direction::Left) pendingDirection Direction::Right; break; default: break; } }3.3 用QPainter把画面画出来绘制这部分是Qt里面最有价值的学习点。你要在paintEvent里用QPainter把网格、蛇身、食物画出来。基本流程void GameWidget::paintEvent(QPaintEvent *) { QPainter painter(this); painter.fillRect(rect(), Qt::black); // 绘制网格线可选增加质感 painter.setPen(QPen(QColor(40, 40, 40), 1)); for (int x 0; x GRID_COLS; x) { painter.drawLine(x * CELL_SIZE, 0, x * CELL_SIZE, height()); } for (int y 0; y GRID_ROWS; y) { painter.drawLine(0, y * CELL_SIZE, width(), y * CELL_SIZE); } // 绘制食物 painter.setBrush(Qt::red); painter.drawRect(QRect(food.x * CELL_SIZE, food.y * CELL_SIZE, CELL_SIZE, CELL_SIZE)); // 绘制蛇身 for (int i 0; i snake.size(); i) { if (i 0) painter.setBrush(QColor(80, 220, 100)); // 蛇头更亮 else painter.setBrush(QColor(50, 160, 80)); painter.drawRect(snake[i].x * CELL_SIZE, snake[i].y * CELL_SIZE, CELL_SIZE, CELL_SIZE); } }绘制不复杂但有几个细节我提炼一下不要去初始化QRect等临时对象后直接用性能虽然有优化但养成好习惯在循环里复用临时对象避免不必要的构造析构开销。虽然贪吃蛇的绘制量很小但作为Qt开发习惯后续画大量图元时差别就出来了。不要在主线程之外去修改Qt的绘制对象。如果你开了多线程网络请求然后直接在线程里调用update()那属于线程不安全操作轻则刷新闪烁重则程序崩溃。正确的姿势是跨线程通讯用信号槽让界面线程在自身上下文中触发update()。不要在一帧里做太重的逻辑运算。QTimer触发moveSnake()时如果逻辑复杂到超过定时器间隔那界面还是会卡。这个问题在贪吃蛇规模里不会出现但做大型项目时QTimer慢于间隔的情况要额外设计跳过逻辑帧或者累积时间再推进别让它变成雪崩。3.4 食物生成的避坑逻辑食物生成看似简单其实有个大坑新生成的食物如果落在蛇身上玩家就永远吃不到了。我第一次写的时候直接随机坐标跑了一分钟发现蛇绕着地图走但食物总是消失不见排查半天才明白是食物刷进了蛇体内部。正确算法是void generateFood() { do { food {rand() % GRID_COLS, rand() % GRID_ROWS}; } while (isSnakeNode(food)); }最坏情况是地图几乎被蛇占满do-while循环会非常慢。理论上当蛇的长度接近整个格子数时随机碰撞概率几乎是1此时这个循环会卡很久。我用一个改成先收集所有空位再从空位里随机选一个的算法来兜底void generateFoodSafe() { QListSnakeNode freeCells; for (int x 0; x GRID_COLS; x) { for (int y 0; y GRID_ROWS; y) { if (!isSnakeNode({x, y})) freeCells.append({x, y}); } } if (!freeCells.isEmpty()) { food freeCells[rand() % freeCells.size()]; } else { // 蛇占满全图游戏胜利 gameOver true; // 或单独处理win状态 } }这个版本的时间复杂度是O(格子数)无论蛇多长都不会卡顿而且顺手处理了蛇占满全部格子这种极致情况。平时写代码可能遇不到这个场景但在游戏项目里这种边界条件往往会回来咬你一口。4. 界面布局、手感与视觉细节打磨逻辑代码跑通后游戏其实只能算能玩。要想好玩还得在界面布局、交互反馈和视觉细节上打磨一番。4.1 用纯代码搭界面还是用Qt Designer贪吃蛇这种界面非常简单的项目我推荐直接纯代码搭建界面。QMainWindow里加一个CentralWidget再放一个VerticalLayout上面是得分和状态标签中间是GameWidget下面是操作按钮开始、暂停。代码量很少逻辑一眼能看全手动调整间距和布局比拖拽控件更可控。很多人学Qt习惯了一上来就用Designer但Designer生成的是XML再转代码遇到需要程序化创建控件、动态布局、或者需要反序列化到手写代码里的场景会有点别扭。我的建议是像贪吃蛇这种界面元素固定的项目亲手写一写布局代码对理解Qt布局系统很有帮助。MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { gameWidget new GameWidget(this); scoreLabel new QLabel(QStringLiteral(得分: 0), this); statusLabel new QLabel(QStringLiteral(按任意方向键开始), this); startBtn new QPushButton(QStringLiteral(开始), this); pauseBtn new QPushButton(QStringLiteral(暂停), this); pauseBtn-setEnabled(false); connect(startBtn, QPushButton::clicked, gameWidget, GameWidget::startGame); connect(pauseBtn, QPushButton::clicked, gameWidget, GameWidget::togglePause); auto *layout new QVBoxLayout; auto *topLayout new QHBoxLayout; topLayout-addWidget(scoreLabel); topLayout-addStretch(); topLayout-addWidget(statusLabel); layout-addLayout(topLayout); layout-addWidget(gameWidget); auto *btnLayout new QHBoxLayout; btnLayout-addStretch(); btnLayout-addWidget(startBtn); btnLayout-addWidget(pauseBtn); btnLayout-addStretch(); layout-addLayout(btnLayout); auto *central new QWidget(this); central-setLayout(layout); setCentralWidget(central); }有个细节一定要处理按钮会抢占键盘焦点。如果你给按钮设置了焦点那么按空格键时触发的是按钮点击而不是游戏暂停。最简单的办法是所有按钮都setFocusPolicy(Qt::NoFocus)让键盘事件全部落到主窗口或游戏画布上。4.2 用样式表提升感官体验QSSQt样式表是提升界面质感的利器写法和CSS很相似。贪吃蛇项目的界面元素不多但一个简洁的深色背景加圆角按钮观感提升非常明显setStyleSheet(R( QWidget { background-color: #2b2b2b; color: #f0f0f0; } QLabel { font-size: 16px; padding: 4px; } QPushButton { background-color: #3a3a3a; border: 1px solid #5a5a5a; border-radius: 6px; padding: 8px 20px; } QPushButton:hover { background-color: #4a4a4a; } QPushButton:disabled { background-color: #333333; color: #888888; } ));有人喜欢在游戏结束弹一个QMessageBox提示分数并询问是否再来一局这也可行。但如果你不想让弹窗阻塞游戏流程可以自己画一个简单的结束状态提示在GameWidget上同时按任意键重新开始。这里插一个很多人问过的点QMessageBox::information能不能设置超时自动关闭标准API是不行的QMessageBox本身没有超时接口。如果你真想实现3秒没操作自动关闭这种效果可以重写一个对话框或者用QTimer在调用exec()的本地事件循环外关掉它。但如果你的游戏本来就有明确的结束界面其实没必要用QMessageBox直接在界面上显示状态即可既省事又美观。4.3 分数、速度与难度曲线光吃食物不加难度游戏玩两分钟就容易腻。稍微加一点动态变化可玩性立刻不同每吃一个食物加10分每吃5个食物速度提升一档即QTimer的interval减少一定毫秒记一个最高分用QSettings存到本地下次启动还能看到历史纪录速度曲线的设计我踩过坑。如果你直接把interval从150ms直线降到20ms玩家会觉得蛇突然失控因为人的反应速度是有极限的。我用的方案是分段下降比如15档速度每档之间间隔5~10ms的差别最后一档收在60ms左右不再往下。这样难度曲线是平滑且有节奏感的。计算分数的显示可以用QString::number(sc)转成字符串或者用QString::asprintf(%d, sc)都很方便。没有涉及浮点数精度问题时不用纠结double转字符串分数就是个整数用哪个都行。5. 常见报错与排查思路全是实战记录写这个项目过程中我遇到过不少问题有些是Qt特有的有些是C的通病统一整理出来你遇到类似情况可以直接照猫画虎。5.1 编译阶段的疑难杂症错误C2001常量中有换行符。这通常出现在中文字符串包含在源文件里而文件编码保存成了GBK而非UTF-8。Qt 5默认按UTF-8解析源文件如果IDE保存时转成GBK编出来的常量就有问题。解决一切源代码统一用UTF-8编码保存或者在关键中文字符串上使用QStringLiteral包装。MSB8020找不到v142工具集。如果你装了VS新版本但项目还在用旧平台工具集会碰到这个错误。解决在项目的*.vcxproj里把PlatformToolset改成Current或者在Qt Creator的Kits设置里重新选择一个与已安装VS匹配的Qt版本。无法解析的外部符号。通常是链接阶段出了问题你调用了某个模块的类却忘了在.pro文件里加对应的QT 模块名比如跑了QPainter但.pro里漏写QT widgets。遇到这个先检查模块声明再考虑是不是库路径没配对。5.2 运行崩溃的三大元凶Qt程序的崩溃提示往往让人一头雾水因为Qt会把错误类型转成异常信息比如ASSERT failure in QVector::operator[]: index out of range。贪吃蛇项目里崩溃最常见就这么几类容器越界。蛇身体的索引访问超出范围尤其在初始化时还没插入任何节点就访问snake[0]。根治方法是所有关键存取之前都先判断size()或者用at()加越界检查。悬空指针和野指针。new出来的对象忘了delete或者delete后还留着指针并用。现代C建议直接用智能指针std::shared_ptr或std::unique_ptrQt这边也可以用QSharedPointer。贪吃蛇项目里析构顺序也很关键主窗口关闭时确保所有子对象都被释放子对象释放时不要再去操作父对象。非UI线程操作界面。如果你把moveSnake()放到了std::thread里执行再直接调用update()想刷新运气好是没反应运气差是直接崩。Qt规定UI只能在主线程操作跨线程必须用信号槽或QMetaObject::invokeMethod这是个铁律。5.3 经典报错速查表下面这张表是我把开发过程中常遇到的报错和解决方案汇总出来的可以直接当速查手册用报错内容原因定位解决思路找不到Qt5Core.dll / 应用程序无法启动运行时依赖缺失用windeployqt部署或把Qt bin目录加入PATH0xc000007b 应用无法正常启动64位/32位库不匹配或运行库损坏检查Kit和操作系统的架构匹配重装VC运行库ASSERT failure in QVector::at访问越界检查循环边界尤其注意蛇长度变化中文乱码显示为问号或方块源码编码问题源文件存UTF-8用QStringLiteral链接时找不到qtmain.libMSVC套件配置问题确保选择了对应Qt的编译器检查Kit窗口显示出来后立刻消失局部变量被析构检查对象的生命周期别把窗口对象建在栈上用完即毁程序编译通过但运行后界面无响应事件循环被阻塞检查是否用了while(1)死循环或者耗时操作阻塞了UI线程键盘按键无效焦点不在接收事件控件上统一处理焦点策略或者在主窗口拦截按键事件画面撕裂/闪烁直接绘制导致重绘不完整用Qt默认的双缓冲机制不要在paintEvent之外到处直接刷5.4 调试过程中的几个小习惯写这类图形和逻辑混在一起的小游戏调试方式跟业务代码很不一样。给几个我自己的习惯逻辑数据优先可视化。在界面上临时打印一些调试信息比如蛇头坐标、当前方向、状态机的值。放在角落里比断点直观得多。我常用的做法是设置一个debugLabel显示这些信息调试完再隐藏。故意放慢速度调试。把定时器间隔临时调到500ms这样每个状态变化肉眼都能跟上很多逻辑漏洞一眼就能看出来。正常速度下很难观察蛇头是不是先撞了自己才吃食物这种前后顺序问题。善用qDebug()但及时清理。qDebug()很好用但发布前大批量注释掉不要留在代码里影响性能。另外qDebug()默认往stderr输出看清楚调试器里的输出面板别看了半天的qDebug信息还以为是程序没跑起来。6. 发布与分发把游戏给别人玩开发完只是第一步想发给同事朋友玩就得处理发布和部署。这一步本身就是Qt开发中的一个核心技能点因为Qt程序依赖一堆动态库和插件直接拷一个exe过去大概率跑不起来。6.1 用windeployqt一键部署依赖Qt官方提供了一个部署工具叫windeployqt。在命令行里这样用cd /d 你的exe所在目录 C:\Qt\5.15.2\msvc2019_64\bin\windeployqt.exe 你的游戏.exe这个工具会自动扫描exe依赖的Qt模块把对应的DLL和插件复制到同目录。跑完之后你会发现目录下多了一堆文件和文件夹platforms、styles等。不要嫌文件多这些都是运行必需的内容。整个文件夹拷给别人双击exe就能玩。如果需要检查是否还有漏掉的第三方DLL可以用Dependencies工具扫一下exe的导入表看哪些库还没被带到目标机器上。我遇到过一种情况程序里用到了Qt的svg模块但主exe里没有显式调用windeployqt默认扫不出来运行到某个功能才报错。这时候手动把qsvgicon.dll和相关模块拷过去即可。6.2 发布前的收尾工作发布前别忘了几件事版本切换。开发时用Debug模式发布要用Release模式否则DLL依赖会带调试符号体积大且可能要求目标机装有调试运行时。图标和资源。把资源文件用Qt资源系统编译进可执行文件不要依赖外部图片文件这样分发时只需要一个目录连图片都不用带。程序说明。即使只是个小游戏一个简单的README文档写清楚启动方式、按键说明也能让别人更快上手。还有一点要注意如果用MSVC编译并且目标机器上没装VC运行时程序启动还是会报错。这时你可以选择在分发包里带上对应的vc_redist.x64.exe让用户先安装或者提前装好运行库再打包。静态编译把依赖全部编进exe虽然可行但Qt是LGPL协议如果你用了开源许可的Qt库做静态链接软件本身的许可合规性需要额外考虑公开分发前务必自查一下协议要求。提示windeployqt之后目录不要乱删。platforms目录如果没了程序启动时报could not find or load the Qt platform plugin windows是部署时最容易踩的坑之一。7. 这个项目还能怎么扩展贪吃蛇做完之后扩展方向其实非常多。想加深Qt功底可以从这几个角度继续折腾玩法层添加穿墙模式、障碍物关卡、双人模式技术层把渲染从QPainter切成QGraphics View框架或者QOpenGLWidget感受不同绘图方案的区别数据层把最高分存储从QSettings换成SQLite顺带引入一些数据库操作的经验架构层把控制、视图和逻辑拆得更加模块化甚至引入依赖注入的思路我个人实际做的时候最受益的不是游戏本身而是对Qt事件循环、定时器驱动模型和数据与视图分离有了更直觉的理解。以后再写Qt桌面工具遇到表格大数据渲染卡顿第一反应就是看数据和视图是否耦合了遇到程序假死第一反应就是看哪段耗时操作占了UI线程。这种思维迁移能力比单纯做完一个小游戏有价值得多。如果你正卡在环境配置上先照着第一部分稳扎稳打把环境装好别急着跳代码。如果你已经能跑通基础版那我建议你接下来加一两个扩展功能比如障碍物或双人模式那比反复重写同一版贪吃蛇更能巩固你的Qt技能树。