简介:这是一份结合C语言基础与Qt5框架实现的打地鼠游戏完整源码,主要面向需要提交期末大作业或课程设计的计算机专业学生,也适合刚接触Qt图形界面编程的初学者参考。项目为98分高分大作业,核心玩法涵盖随机出洞、鼠标点击判定、锤子起落动画状态切换与分数统计等模块,代码注释清晰,下载后简单配置即可运行。资源包共22个文件、约226KB,其中源码、界面与工程配置以cpp、h、ui、pro、qrc为主,另含png/jpg格式的界面背景与图标素材,整体结构紧凑,便于按场景、交互、主窗口等模块阅读和二次修改。目前已有50人学习浏览,对于正在寻找功能完整、界面美观且便于答辩讲解课设项目的学生来说,这份源码可直接作为项目雏形,也适合继续扩展关卡难度、增加计分规则或更多交互效果。
1. 打地鼠课设只有控制台怎么行:C语言逻辑和Qt5界面才是拿高分的关键
刷C语言课程设计的时候,打地鼠这个题目总会被那几个Ctrl+C的文件糊弄过去:黑乎乎的控制台里打印几个数字,就算“打中了”。想拿到98分这种档次,必须用Qt5把界面和交互拉起来,再让C语言负责真正能被老师盘问的逻辑。这篇笔记把“基于C语言的Qt5打地鼠游戏源码”从选型到打包拆成六个步骤,重点说清信号槽怎么接、C函数怎么被Qt调用,以及最容易让程序翻车的中文乱码和资源路径问题。适合正在做C语言课程设计、又想用Qt5写小游戏但不打算碰C++高级特性的同学。
2. 先拆需求:打地鼠游戏从及格到98分要补的六个细节
2.1 分数构成:从“能跑”到“98分”还差哪些功能
大作业评分通常不是看代码量,而是按功能完整度、界面美观度、交互流畅度、代码规范和答辩表现来打。同一个打地鼠,普通提交和90分以上的提交,差别非常大。下面这个表是按大学课设最常见的评分维度整理的,也是我每次带学弟做项目时必用的清单。
| 评分维度 | 及格水平 | 98分的要求 |
|---|---|---|
| 核心玩法 | 一个地鼠随机出现,点击加分 | 9个洞口、出现/隐藏节奏分离、难度递增 |
| 界面 | 窗口标题栏加几个按钮 | 3x3洞穴布局、圆角边框、锤子光标、清晰图标 |
| 交互 | 只能点一次,没反馈 | 打中特效、音效、分数动画、倒计时到0自动结束 |
| 代码组织 | 一个main.cpp写到底 | 游戏逻辑抽成C文件,头文件接口清晰 |
| 健壮性 | 乱点不崩就行 | 可重复开始、最高分记录、文件路径正确 |
98分不是靠炫技,而是把每个环节做出“我考虑到了”的感觉。核心玩法要能重复玩:一轮60秒结束,弹出重新开始的对话框;地鼠不只是随机出现,还要保证同一只不会连续在同一个洞出现;分数要实时刷新,最后一秒结束不要卡顿。这些细节每一项都对应一到两个Qt5类的用法,也是老师爱问的“你这里是怎么实现的”。
对C语言课程设计来说,最容易被忽视的是代码结构。纯C写的控制台程序可以一个函数一个功能,但Qt5本身就是C++框架,如果你把C语言语法和Qt5混在一起,会变成四不像。常见做法是:界面归Qt5管,游戏规则和随机算法归C语言管,中间用一个很薄的头文件做桥接。这样答辩时可以说“我在C语言里实现了核心逻辑,Qt5只负责显示”,一句话就把分工解释清楚了。
2.2 Qt5 + C语言的工程结构:pro文件、main.cpp和信号槽的配合
Qt5的默认语言是C++,但不代表你必须全程用C++的高级特性。打地鼠这类项目,真正需要C++的地方只是继承QMainWindow、写信号槽,其余像是随机数、分数计算、判定规则,完全可以按照C语言的方式写。推荐把工程文件拆成下面这样,每个模块职责单一。
MoleGame/ ├── MoleGame.pro ├── main.cpp ├── mainwindow.h ├── mainwindow.cpp ├── game_logic.h ├── game_logic.c ├── resource.qrc └── images/ └── mole.png首先看.pro文件,qmake靠它决定怎么编译。最关键的是把C文件直接写进SOURCES,别把它放到别的变量里。
QT += core gui widgets TARGET = MoleGame TEMPLATE = app CONFIG += c++11 SOURCES += \ main.cpp \ mainwindow.cpp \ game_logic.c HEADERS += \ mainwindow.h \ game_logic.h RESOURCES += \ resource.qrc这段配置里,game_logic.c会被qmake识别为C源文件,用C编译器而不是C++编译器编译。Qt5会自动处理这种混编,不需要额外指定,但前提是你的C函数要被C++调用,必须用extern "C"包裹声明。CONFIG += c++11是为了能用lambda表达式写信号槽,Qt5默认也支持,如果你面编译器太老,可以去掉这句然后改用传统connect写法。
接下来是C语言逻辑部分。game_logic.h是桥接头文件,先给编译器一个明确的C接口。
#ifndef GAME_LOGIC_H #define GAME_LOGIC_H #ifdef __cplusplus extern "C" { #endif /* 初始化游戏状态,score为int指针 */ void game_init(int *score); /* 生成一个0到max_hole-1的随机洞口编号 */ int game_random_hole(int max_hole); /* 给分数一个变化量,delta可为负数 */ void game_add_score(int *score, int delta); #ifdef __cplusplus } #endif #endif对应的game_logic.c实现用纯C写。这里不需要Qt的任何头文件,方便你在编译前用gcc单独测试。
#include "game_logic.h" #include <stdlib.h> #include <time.h> void game_init(int *score) { *score = 0; srand((unsigned int)time(NULL)); } int game_random_hole(int max_hole) { if (max_hole <= 0) return 0; return rand() % max_hole; } void game_add_score(int *score, int delta) { *score += delta; }这段代码的逻辑很直白:game_init里重置分数并给rand设随机种子,否则每次启动地鼠出现的顺序都一样;game_random_hole用取模把随机值限制在洞口数量内;game_add_score是一个通用加减分函数。你可以在后面扩展出“判断事件响应时间超时”“根据关卡调整速度”等函数,让C部分看起来更厚重。
main.cpp是整个程序的入口,它负责创建窗口并进入Qt事件循环。把main.cpp做得越简单越好。
#include <QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }这里不需要写任何业务代码。QApplication负责初始化Qt运行环境,MainWindow是我们自己写的窗口类,w.show()显示窗口,a.exec()让程序一直响应鼠标和定时器事件。你以后做任何Qt5小游戏,main.cpp基本都可以保持这个样子。
信号槽是这个项目的核心连接器。打地鼠时,鼠标点击按钮,按钮发出clicked信号,Qt把信号路由到我们在MainWindow里写的槽函数,槽函数再去调用C语言函数更新分数。理解这层关系后,Qt的其余部分就只是摆控件的事。
3. 用Qt5搭出打地鼠界面:QPushButton数组、定时器和光标
3.1 用QGraphicsScene还是QWidget?两种画法的选型
打地鼠的界面可以用两种路径实现:一种是用QGraphicsView和QGraphicsScene,把所有地鼠当成图形项放进去;另一种是用QWidget和QPushButton,一个按钮代表一个洞。初学者常在这两个方案之间纠结,我直接把对比列出来。
| 维度 | QGraphicsScene | QWidget + QPushButton |
|---|---|---|
| 学习成本 | 要先理解场景、图元、坐标系统 | 和普通窗口布局一致 |
| 点击判定 | 重写mousePressEvent,自己算命中 | 按钮自带的clicked信号 |
| 地鼠动画 | 用QGraphicsItem动画方便 | 需要QPropertyAnimation配合 |
| 适合场景 | 需要拖拽、缩放、复杂碰撞的游戏 | 规则明确的小游戏 |
打地鼠的交互就是“点一下某个固定位置”,没有拖拽、没有缩放,用QPushButton是最高效的。每个按钮天然支持点击,也天然具备“按下-弹起”的状态,连锤子光标都只需要一行setCursor。QGraphicsScene那套系统适合做贪吃蛇、扫雷这类需要自由绘制网格的,或者你想让地鼠从洞里探出头、有连续位置变化的动画。如果你的课设评分标准里明确说“画面精美”,那可以混合用:QWidget铺底,QGraphicsView只做动画窗口。但从工作量回报来看,QPushButton撑住核心已经足够。
3.2 关键代码:9个洞、地鼠出现与隐藏的定时器
确定了用按钮,接下来就要把它们排成3x3并管理状态。在MainWindow的头文件里,需要声明按钮数组、定时器、分数标签和记录每个洞是否有地鼠的bool数组。下面的mainwindow.h是一个能跑通最小版本的完整设计。
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QPushButton> #include <QLabel> #include <QTimer> class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void showMole(); void hideMole(); void onHoleClicked(int index); void updateTimer(); private: QPushButton *holeButtons[9]; QLabel *scoreLabel; QLabel *timeLabel; QTimer *showTimer; QTimer *hideTimer; QTimer *countdownTimer; bool moleActive[9] = {false}; int score; int timeLeft; }; #endif这里用了Q_OBJECT宏,这是Qt信号槽机制的元信息标记。没有它,所有connect都不会生效,而且编译时会报vtable相关错误。showTimer负责定期让某个洞出现地鼠,hideTimer负责把地鼠藏起来,countdownTimer负责倒计时。把score和timeLeft作为普通int成员变量,配合C语言函数来操作。
在mainwindow.cpp的构造函数里,创建9个按钮,把它们放进网格布局,并把点击事件和索引绑定起来。这里用lambda表达式是最简洁的写法。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), score(0), timeLeft(60) { QWidget *central = new QWidget(this); setCentralWidget(central); QGridLayout *grid = new QGridLayout; grid->setSpacing(10); for (int i = 0; i < 9; ++i) { holeButtons[i] = new QPushButton(this); holeButtons[i]->setFixedSize(90, 90); holeButtons[i]->setStyleSheet("QPushButton { border: 3px solid #8B4513; border-radius: 45px; background: #D2B48C; }"); holeButtons[i]->setCursor(Qt::PointingHandCursor); connect(holeButtons[i], &QPushButton::clicked, this, [this, i]() { onHoleClicked(i); }); grid->addWidget(holeButtons[i], i / 3, i % 3); } scoreLabel = new QLabel(QString("分数: %1").arg(score), this); scoreLabel->setStyleSheet("font-size: 20px; font-weight: bold;"); timeLabel = new QLabel(QString("剩余时间: %1").arg(timeLeft), this); timeLabel->setStyleSheet("font-size: 20px; color: #CC0000;"); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(scoreLabel); mainLayout->addLayout(grid); mainLayout->addWidget(timeLabel); central->setLayout(mainLayout); showTimer = new QTimer(this); hideTimer = new QTimer(this); countdownTimer = new QTimer(this); connect(showTimer, &QTimer::timeout, this, &MainWindow::showMole); connect(hideTimer, &QTimer::timeout, this, &MainWindow::hideMole); connect(countdownTimer, &QTimer::timeout, this, &MainWindow::updateTimer); showTimer->start(900); hideTimer->start(1200); countdownTimer->start(1000); game_init(&score); }构造函数的逻辑分三块:先是创建并排列9个按钮,把每个按钮的点击信号绑到onHoleClicked(i),注意lambda里[this, i]捕获了两个变量,这样槽函数才能知道点击的是第几个洞。然后是分数和时间的标签,以及上下布局。最后创建三个定时器,各自设置触发间隔,并调用C语言的game_init初始化分数和随机数种子。
这里有个参数权衡值得说:showTimer设900毫秒,hideTimer设1200毫秒,会造成“地鼠刚显示不到1200毫秒就被强制隐藏”的结果。地鼠的可见时间由hideTimer决定,而不是showTimer。如果想做难度递增,只需要在下一关把showTimer的间隔缩短,同时把hideTimer缩短,就能让地鼠出现得更频繁、停留得更短。
3.3 界面布局:计时标签、分数和按钮的摆放次序
界面的观感会影响老师的第一印象。Qt5里最简单的布局方式是从上到下放一个垂直布局,顶部放分数和倒计时,中间放3x3按钮矩阵,底部放一点说明文字。上面代码里已经把垂直布局搭好了,但要注意一个细节:把scoreLabel放在网格布局外面、整个窗口的最顶部,这样视线顺势从上向下走,先看到分数变化,再去找地鼠,交互符合直觉。计时标签放在网格下方,是因为倒计时到0时需要弹出消息框,放在下方不容易被按钮误触。
如果你还想加“开始”“重新开始”按钮,建议不要把这两个按钮放进网格,而是要放在scoreLabel和grid之间。开始按钮默认为禁用,倒计时结束后再启用,这是评分标准里“人机交互完整”的一个加分点。布局器本身不复杂,QGridLayout配合QVBoxLayout足够,不需要去手动计算位置。
4. 游戏逻辑与事件处理:信号槽、随机生成和难度曲线
4.1 信号槽机制:让地鼠被点击时真正“打”到
Qt5的按钮点击链路是这样的:用户按下鼠标,系统识别到按钮区域,按钮发出clicked信号,信号连接到的槽函数被执行。打地鼠的关键是在槽函数里判断“这个洞现在是否有地鼠”,有就加分并隐藏,没有就不理会。下面是onHoleClicked的实现。
void MainWindow::onHoleClicked(int index) { if (index < 0 || index >= 9) return; if (moleActive[index]) { game_add_score(&score, 10); scoreLabel->setText(QString("分数: %1").arg(score)); moleActive[index] = false; holeButtons[index]->setIcon(QIcon()); holeButtons[index]->setStyleSheet( "QPushButton { border: 3px solid #8B4513; border-radius: 45px; background: #D2B48C; }" ); } }这里做了三件事:下标合法性检查,这是对异常情况的防御;调C语言的game_add_score更新分数成员变量;把按钮上的图标清空并恢复洞的颜色。moleActive数组是界面和逻辑之间的状态同步器,打中了第一时间置false,可以防止快速双击同一个洞加两次分。
有的同学会用setEnabled(false)来禁止点击,但那样会让按钮变成灰色,影响外观。正确做法是保留按钮可点击,用moleActive判断是否有效。这也是为什么要把状态单独抽出来,而不是让按钮的enabled属性承担“有没有地鼠”的信息。
4.2 随机性与难度曲线:用QTimer和rand控制出现频率
地鼠出现不能有固定顺序,否则玩家背板就能拿高分。随机数用之前game_random_hole生成,但要注意Qt定时器本身也有触发延迟,随机数分布太差会影响手感。常见做法是每次showMole里重新随机,并加一个简单去重逻辑。
void MainWindow::showMole() { int hole = game_random_hole(9); if (moleActive[hole]) return; moleActive[hole] = true; holeButtons[hole]->setIcon(QIcon(":/images/mole.png")); holeButtons[hole]->setIconSize(QSize(70, 70)); }这里如果抽到已经有地鼠的洞,就直接跳过,不“强行换一个”。表面上看起来浪费了一次机会,实际上会让地鼠密度更自然。你要注意setIconSize的尺寸应与按钮大小匹配,90x90的按钮放70x70的图标,留有边距。
难度曲线建议做成三档,而不是每过几秒就变化一次。常见做法是用一个level变量控制,每20秒升一级。在updateTimer里同时负责倒计时和难度切换。
void MainWindow::updateTimer() { timeLeft--; timeLabel->setText(QString("剩余时间: %1").arg(timeLeft)); if (timeLeft <= 20) { showTimer->setInterval(600); hideTimer->setInterval(800); } else if (timeLeft <= 40) { showTimer->setInterval(800); hideTimer->setInterval(1000); } else { showTimer->setInterval(900); hideTimer->setInterval(1200); } if (timeLeft <= 0) { showTimer->stop(); hideTimer->stop(); countdownTimer->stop(); QMessageBox::information(this, "时间到", QString("你的分数是 %1").arg(score)); timeLeft = 60; score = 0; updateTimer(); showTimer->start(); hideTimer->start(); countdownTimer->start(); } }这里把难度分成三个区间:最后20秒最快,中间20秒稍快,前20秒正常。定时器的间隔参数直接决定手感,不同屏幕大小、不同地鼠图标的视觉大小,都会影响玩家能否及时点中。建议自己试玩几轮,如果觉得点不到,就把showTimer间隔加到700以上,别让地鼠出现间隔低于500毫秒,否则随机位置加上人眼反应时间会让游戏变得不可玩。设置间隔后,地鼠停留时间的上限由hideTimer决定,所以难度越高,hideTimer也要同步缩短,不然地鼠会叠在一起出现。
随机函数在最开始播种后,每次调用rand都是同一个序列,这在调试阶段是好事,可以复现问题。如果你想每次启动都不同,可以把srand放在game_init里,我们已经这么做了。
4.3 声响与动画:QSoundEffect和QPropertyAnimation的简单接入
98分作业一般要求有音效和简单动画。Qt5里有专门的QSoundEffect类,用来播放WAV文件,用法不复杂。在构造函数里加载音频,然后在打中时播放。
#include <QSoundEffect> QSoundEffect *hitSound; hitSound = new QSoundEffect(this); hitSound->setSource(QUrl::fromLocalFile(":/sounds/hit.wav")); hitSound->setVolume(0.6f);然后在onHoleClicked加分后加上hitSound->play();。注意QSoundEffect只支持未压缩的WAV格式,如果你用的是MP3,要换成QMediaPlayer或者转格式。对大作业来说,音效不是核心,但没有会显得糙,所以至少要给打中加一个简短音效。
动画可以给地鼠按钮加一个“弹跳”效果,用QPropertyAnimation修改按钮的最大尺寸。不过QPropertyAnimation对QWidget的geometry属性操作在按钮布局中不太好控制,容易打乱布局。更稳妥的做法是只对QIcon的显示做切换,或者给按钮设置一个不改变布局的样式变化。比如打中瞬间设置背景色为红色,然后通过QTimer延时恢复,效果简单还不会影响布局。
void MainWindow::onHoleClicked(int index) { // ... 原有加分逻辑 ... holeButtons[index]->setStyleSheet( "QPushButton { border: 3px solid #FF0000; border-radius: 45px; background: #FFD700; }" ); QTimer::singleShot(150, this, [this, index]() { holeButtons[index]->setStyleSheet( "QPushButton { border: 3px solid #8B4513; border-radius: 45px; background: #D2B48C; }" ); }); }QTimer::singleShot能在指定时间后执行一次槽函数,适合做这种“临时状态恢复”的动画。这样不用额外维护成员变量,代码也简洁。这里150毫秒是一个视觉上比较舒服的反馈时间,太短看不出效果,太长会让下一次点击被判为无效。
5. 大作业避坑指南:Qt5+C语言项目常见的5个翻车现场
5.1 现象:Qt Creator里中文乱码,显示成方块
很多同学用Windows上的MSVC编译器,代码文件如果保存成UTF-8,MSVC默认可能按GBK去解析,结果中文字符串变成乱码,甚至编译报警告。还有的是把源文件另存为ANSI,但Qt5内部默认UTF-8,两种情况下都会出问题。
原因本质是“源文件编码”和“编译器读取编码”不一致。解决方法是统一用UTF-8编码保存所有源文件,并在.pro文件里加一行让MSVC支持UTF-8:在Windows下MSVC编译器,qmake会自动加上/utf-8标志,前提是Qt版本不能太旧。如果还在用Qt 5.9以下,你可以手动把需要中文的文件另存为UTF-8,并且在字符串外面包一层QStringLiteral。
scoreLabel->setText(QStringLiteral("分数: %1").arg(score));QStringLiteral告诉编译器这是一段UTF-8字面量,在不同平台都能正确转换。这个小小的改动能避免大部分中文乱码问题。另外,资源文件里的qrc路径中如果有中文文件名,也会出现类似现象,所以图片、音频文件一律用英文名。
5.2 现象:点击地鼠没反应,信号槽连接失效
明明按钮显示了地鼠,点击却没有加分。先检查按钮的connect有没有执行。常见问题是把connect写在了按钮创建之前,或者类的头文件里漏了Q_OBJECT宏。
信号槽建立的前提是类和QObject之间完成元信息注册,Q_OBJECT宏就是触发moc程序生成代码的开关。如果头文件里有Q_OBJECT,但没保存后重新跑qmake,也会出现“点击无反应”。解决步骤是:先确认类声明中有Q_OBJECT,然后执行qmake重新生成Makefile,最后重新构建。如果用了lambda,还要确认编译时开启了C++11,否则lambda根本不会被编译,connect也不会执行。
另一个容易踩的坑是:按钮的Icon为空,但moleActive已经设为true。这种情况界面看不到地鼠却点击有效,比没反应更难查。建议在showMole里打一个qDebug输出洞号和图标状态,用调试器看状态变量。
5.3 现象:编译报错“undefined reference to vtable”
这个报错常出现在Qt项目里,而且新手一看就慌。vtable是虚函数表,Qt的QObject类通过它实现信号槽和对象属性。报错原因通常是:类头文件含有Q_OBJECT宏,但没有生成对应的moc文件,或者Makefile没有重新生成。
解决方法是:删除build目录里的所有中间文件,然后重新qmake并编译。如果你是在Qt Creator里,直接右键项目,选择“执行qmake”,再重新构建。如果是命令行,进入build目录运行qmake 项目路径再make。让qmake重新扫描头文件时,moc会生成对应的元信息实现代码,vtable引用就会被解析。注意不要手动删除moc文件后只编译不qmake,那样问题依旧。
5.4 现象:地鼠贴图出现白底,透明PNG不生效
你找到的图片默认有白色背景,放进按钮后像个白色方块贴在洞口上,非常丑。原因有两个:图片本身是JPG或者没有alpha通道的PNG;或者按钮的背景色不透明,把图片的透明区域又盖住了。
先用PS或在线工具确认图片底色是透明格子。然后在按钮样式表里强制设背景透明:
holeButtons[i]->setStyleSheet( "QPushButton { border: none; background: transparent; }" );注意如果同时还要保留洞穴的棕色边框,就得把边框和背景分开写。你可以用两层结构:外层洞用一个QLabel放背景,内层按钮放地鼠图且背景透明。对大作业来说,简单点就统一用带alpha的PNG图片作为按钮图标,并让按钮的背景色与洞背景一致。透明通道的问题还会出现在qrc资源加载上,如果图片没有正确打包进qrc文件,图标会显示为空白,这时在代码中要用:/images/mole.png这种路径,而不是相对路径。
5.5 现象:最高分重启后丢失,文件读写权限失败
很多课设要求记录最高分,做法是程序退出前写一个txt文件,下次启动读出来。但用相对路径写文件时,默认写的是“当前工作目录”,而用Qt Creator运行时,工作目录是build文件夹,不是源码目录,别人点开exe时可能又变成exe所在目录,这样一来文件位置飘忽不定,容易权限不足。
解决方法是不要自己拼路径,用Qt提供的标准路径接口。
#include <QStandardPaths> #include <QFile> QString filePath = QStandardPaths::writableLocation( QStandardPaths::AppDataLocation ) + "/highscore.txt"; QDir().mkpath(QFileInfo(filePath).absolutePath());AppDataLocation在不同系统下会自动指向用户可写的数据目录,Windows下是C:/Users/用户名/AppData/Roaming/项目名。这样程序换机器、换用户都不会出现文件写不进的情况。很多打地鼠大作业都栽在“运行一次后第二次打开读不到最高分”这个细节上,你提前用了标准路径,这个分数就稳拿了。
6. 让大作业从95分到98分:QSS美化、打包和答辩的三个技巧
6.1 用QSS把普通按钮变成地鼠洞
QSS是Qt的样式表语法,类似CSS。给按钮写一套复杂样式,不用贴图也能做出洞穴的立体感。下面这段可以让按钮看起来像泥洞。
QPushButton#HoleButton { border: 3px solid #5D3A1A; border-radius: 45px; background-color: qradialgradient( cx:0.5, cy:0.4, radius:0.5, fx:0.5, fy:0.4, stop:0 #6B3F22, stop:0.7 #3E2723, stop:1 #2D1B12 ); } QPushButton#HoleButton:hover { background-color: qradialgradient( cx:0.5, cy:0.4, radius:0.5, fx:0.5, fy:0.4, stop:0 #7A4A2A, stop:0.7 #4E342E, stop:1 #3A231C ); }要在构造函数里给按钮设置对象名setObjectName("HoleButton"),然后在整个窗口上设置setStyleSheet;按对象名选择器是Qt5里最可控的一种方式。注意qradialgradient的写法不要拼错,参数名是cx, cy, radius, fx, fy,一旦半径和圆心设置不当,按钮会变成一块黑斑。改完后用border-radius: 45px配合90x90的尺寸做圆角。图标显示时,按钮的样式会保留,地鼠图盖在渐变背景上,比单纯灰底好看得多。
6.2 用windeployqt打包发布:让程序在没装Qt的机器上也能跑
这一步对课设很重要,因为老师不一定有开发环境。在Qt Creator里先用Release模式构建一遍,然后到输出目录里找到MoleGame.exe。接着打开命令提示符,进入这个目录,执行windeployqt。这个工具会自动把运行所需的Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll和平台插件复制到exe旁边。
C:\Qt\Qt5.15.2\bin\windeployqt.exe D:\build-MoleGame-Release\release\MoleGame.exe运行后,整个exe所在目录就是可发布文件夹。压缩成zip发给老师,双击exe就能跑。要提醒的是windeployqt不会打包qrc资源,因为资源文件已经被编译进exe里,所以不用额外复制images和sounds。如果你用了QSoundEffect,它依赖Qt5Multimedia的dll,如果链接没有把这些库打进去,运行时会提示找不到,打包前先在一台没装Qt的虚拟机里测试一次。
这里也顺便解决一个常见误区:静态编译可以让exe一个人扛下所有,但静态编译Qt需要自己用源码重新编译整个Qt库,耗时几小时,对课设而言性价比不高。动态部署用windeployqt就够,除非老师只允许交一个exe文件,否则没必要静态编译。
6.3 答辩演示的小剧本:3分钟讲清楚你的设计
答辩时间有限,别一上来就念代码。我的习惯是先用30秒演示游戏效果,让老师看到界面和分数变化;再用一分钟讲架构,具体说C语言game_logic.c和Qt5窗口是怎么分离的;最后一分钟讲一个你实际踩过的坑。
演示效果时要有意识地展示难点,比如倒计时最后几秒地鼠出现明显变快,这时鼠标动作也要快,老师会觉得你对难度曲线有控制。讲架构时,拿着pro文件说明game_logic.c用C文件编译,extern "C"的桥接方式,这块是最能证明“基于C语言”的部分。踩坑可以讲5.2那个“信号槽连接失效”的问题,真实的问题总是更有说服力。
我已经习惯在答辩前把所有字符串都改成英文标签,这样入场后不用解释中文乱码问题,也显得专业。当然,如果你的界面支持中文显示,提前打开程序确认一遍字体即可。答辩结束前把程序编译成Release版本放在桌面,老师如果想拷走一份,随时可以给,这比你说“代码在U盘里”要加分。
回头看我去年优化这个项目的过程,最大的收获不是学会了多少个类,而是把C语言逻辑和Qt5界面切成两层后,调试和改功能都干净多了。希望你这次的大作业也能跑得顺,答辩时心里有底。希望帮到你。
本文还有配套的精品资源,点击获取