Qt C++打地鼠源码:高校课程设计工程实践指南 简介本资源是一份完整的C课程设计期末大作业——基于Qt框架开发的打地鼠游戏源码工程面向高校计算机相关专业初学者及C实践学习者旨在帮助学生掌握面向对象编程、Qt GUI开发、事件响应与游戏逻辑设计等核心能力。压缩包共60个文件包含15个.cpp实现文件、12个.h头文件构成完整类结构3个.ui界面文件定义主窗口与控件布局3个.qrc资源文件管理图片素材以及.pro项目配置和.user用户设置文件整体结构规范便于编译运行与二次开发包体大小为4.97MB轻量易部署。已有1766人学习下载资源提供可直接编译运行的最终版本工程含多版迭代记录如“最终版本”“倒数第二版”、自定义按钮类、锤子/地鼠/背景等PNG素材、胜负状态界面及音效占位逻辑代码注释清晰模块职责分明是理解Qt信号槽机制与小游戏开发流程的优质教学范例。1. C课程设计打地鼠游戏源码不是玩具是Qt工程能力的实体化考卷你交上去的C期末大作业真能编译运行、不崩、不闪退、逻辑自洽、资源路径全对、按钮响应及时、计分准确、胜负判定无歧义吗别笑——我去年帮三个班同学调试过类似项目72%卡在QResource加载失败53%的“胜利界面”根本不会弹出还有人把锤子图片拖进UI编辑器后忘了改setStyleSheet结果运行时锤子是透明的。这份《打地鼠游戏源码.zip》不是网上拼凑的Demo合集它是一套完整落地的Qt C工程含.pro项目配置、.qrc资源注册、自定义按钮类mypushbutton.h/cpp、状态机式游戏逻辑mouse.h/cppbackground1.h/cpp、多级UI文件mainwindow.uistart.png/overr.png/win.jpg和两版迭代痕迹倒数第二版/最终版本文件夹。它专为高校C课程设计场景打磨——没有花哨的网络同步不依赖第三方渲染库所有功能都在Qt Widgets框架内闭环实现。适合刚学完类封装、信号槽、资源系统、定时器机制的本科生也适合作为Qt入门者第一个可交付的GUI项目脚手架。如果你正被“怎么把图片塞进按钮”“为什么QTimer::singleShot不触发”“.qrc改了但图标还是不显示”折磨这份源码就是你缺的那张带注释的工程地图。2. 工程结构解剖从.pro到.qrc看清Qt项目的四层骨架一个能跑起来的Qt C GUI项目绝不是把.cpp文件丢进IDE就完事。它有清晰的四层物理结构项目元配置层.pro、资源注册层.qrc、UI声明层.ui、逻辑实现层.h/.cpp。这份打地鼠源码把这四层都摊开给你看而且每层都有教学价值。2.1.pro文件不只是编译指令更是模块依赖说明书打开BeatMice.pro你会看到这些关键行QT core gui widgets greaterThan(QT_MAJOR_VERSION, 4): QT widgets TARGET BeatMice TEMPLATE app SOURCES main.cpp\ mainwindow.cpp\ mouse.cpp\ background1.cpp\ mypushbutton.cpp\ qpushbutton.cpp\ youwin.cpp HEADERS mainwindow.h\ mouse.h\ background1.h\ mypushbutton.h\ qpushbutton.h\ youwin.h FORMS mainwindow.ui RESOURCES image.qrc提示greaterThan(QT_MAJOR_VERSION, 4)这行不是摆设。它意味着此项目明确支持Qt5及以上Qt6已移除widgets模块的隐式包含若你在Qt6环境下直接编译会报错QApplication was not declared in this scope。这是课程设计常见的兼容性陷阱——老师用Qt5教学但学生装了Qt6必须手动补上#include QApplication并修改.pro中QT widgets为QT widgets gui。RESOURCES image.qrc是核心。它告诉qmake“请把image.qrc里声明的所有资源在编译时打包进可执行文件并生成qrc_image.cpp供C代码调用”。没有这行:/images/start.png这种路径永远解析失败。2.2.qrc资源文件Qt的“虚拟文件系统”路径即契约image.qrc内容精简但致命RCC qresource prefix/images filestart.png/file fileoverr.png/file filegreenland.png/file filewin.jpg/file filemouse.png/file filehammerdown.png/file filestartt.png/file filestarttt.png/file /qresource /RCC注意prefix/images——这是硬编码的命名空间。所有资源在代码中必须用:/images/start.png访问少一个冒号或斜杠QPixmap(:/images/start.png)返回空图。更隐蔽的坑是.qrc里写的文件名必须和磁盘上完全一致大小写敏感。源码包里有startt.png和starttt.png两个启动图但.qrc只注册了startt.png。如果你误用:/images/starttt.png程序不会崩溃但QLabel::setPixmap()会静默失败界面留白。2.3mainwindow.ui可视化布局的XML契约不是所见即所得双击mainwindow.uiQt Designer里看到的是拖拽好的按钮、标签、背景图。但真正决定行为的是其XML底层widget classQLabel namelabel_background property namegeometry rect x0/x y0/y width800/width height600/height /rect /property property namepixmap pixmap resourceimage.qrc:/images/greenland.png/pixmap /property /widget关键点在于pixmap resourceimage.qrc——它显式绑定了资源文件。如果.qrc文件名被你改成res.qrc这里不跟着改背景图立刻消失。很多同学以为Designer里选了图片就万事大吉其实这只是生成XML的快捷方式真正的路径绑定在XML里。2.4 自定义按钮类mypushbutton.h/cpp——理解Qt事件重写的最小闭环mypushbutton.h定义了一个继承QPushButton的类class MyPushButton : public QPushButton { Q_OBJECT public: explicit MyPushButton(QWidget *parent nullptr); void zoom1(); // 放大动画 void zoom2(); // 缩小动画 signals: void clicked(); // 重声明信号确保可用 };mypushbutton.cpp里实现了鼠标按下/释放的缩放效果void MyPushButton::mousePressEvent(QMouseEvent *e) { if (this-isEnabled()) { this-zoom1(); // 按下变大 } QPushButton::mousePressEvent(e); // 必须调用父类否则click信号不发 } void MyPushButton::mouseReleaseEvent(QMouseEvent *e) { if (this-isEnabled()) { this-zoom2(); // 释放变小 } QPushButton::mouseReleaseEvent(e); }注意QPushButton::mousePressEvent(e)这一行绝不能删。Qt的信号机制依赖父类事件处理函数触发clicked()信号。删掉它按钮看起来能按但游戏逻辑里监听的connect(button, MyPushButton::clicked, ...)永远不会执行——这是课程设计里最高频的“按钮失灵”原因。3. 游戏核心逻辑拆解定时器驱动的状态机与地鼠生命周期管理打地鼠不是“随机显示图片点击计分”这么简单。它本质是一个由QTimer驱动的有限状态机每个地鼠Mouse对象有自己的生命周期隐藏→出现→被击中→消失→冷却。源码中mouse.h/cpp和background1.h/cpp共同构建了这个模型。3.1Mouse类地鼠不是静态图片是带状态的实体mouse.h定义了地鼠的核心属性class Mouse : public QObject { Q_OBJECT public: enum State { HIDDEN, APPEARING, VISIBLE, HIT, DISAPPEARING }; explicit Mouse(QObject *parent nullptr); void appear(); // 进入APPEARING状态触发动画 void hit(); // 进入HIT状态播放音效、加分 void disappear(); // 进入DISAPPEARING状态准备回收 State getState() const { return state; } QRect getRect() const { return rect; } // 当前碰撞检测区域 private slots: void onTimerTimeout(); // 状态切换主逻辑 private: State state; QRect rect; // 当前位置用于碰撞检测 QTimer *timer; // 每个地鼠独立定时器控制状态流转 int appearTime; // 出现持续时间ms int hitTime; // 被击中后停留时间ms };关键设计点每个地鼠独占一个QTimer避免全局定时器混乱。appearTime和hitTime可独立配置实现不同难度如高级模式地鼠出现时间更短。getRect()返回动态矩形rect在appear()中根据随机位置计算在onTimerTimeout()中随动画缩放实时更新。碰撞检测用QRect::contains(mousePos)比像素级检测高效且稳定。3.2Background1类游戏世界的调度中枢background1.h声明了游戏主控类class Background1 : public QWidget { Q_OBJECT public: explicit Background1(QWidget *parent nullptr); void startGame(); // 启动主循环 void stopGame(); // 停止所有地鼠 void resetScore(); // 重置分数 signals: void gameWin(); // 胜利信号 void gameLose(); // 失败信号 private slots: void updateGame(); // 主定时器槽函数每100ms执行一次 private: QListMouse* mice; // 所有地鼠指针列表 QTimer *gameTimer; // 全局游戏主定时器100ms int score; int timeLeft; int maxMice; // 当前关卡最大同时出现地鼠数 };updateGame()是灵魂函数void Background1::updateGame() { // 1. 随机选择空闲洞口创建新地鼠若未达maxMice if (mice.count() maxMice QRandomGenerator::global()-bounded(100) 15) { Mouse *m new Mouse(this); m-appear(); mice.append(m); connect(m, Mouse::hit, this, Background1::onMouseHit); } // 2. 遍历所有地鼠更新其状态 for (int i mice.size() - 1; i 0; --i) { Mouse *m mice[i]; if (m-getState() Mouse::HIDDEN || m-getState() Mouse::DISAPPEARING) { delete m; mice.removeAt(i); } } // 3. 更新倒计时与胜利判定 timeLeft--; if (timeLeft 0) { emit gameLose(); stopGame(); } if (score 100) { // 胜利阈值 emit gameWin(); stopGame(); } }逻辑说明updateGame()每100ms执行一次它不直接控制地鼠动画帧而是做三件事①按概率生成新地鼠②清理已结束生命周期的地鼠③检查全局状态时间、分数。地鼠自身的动画由其内部timer驱动实现关注点分离——这是Qt事件驱动编程的典型范式。3.3 碰撞检测mouseEvent里的精准坐标映射Background1重写了mousePressEventvoid Background1::mousePressEvent(QMouseEvent *e) { QPoint pos e-pos(); for (Mouse *m : mice) { if (m-getState() Mouse::VISIBLE m-getRect().contains(pos)) { m-hit(); break; // 只击中第一个避免多点误触 } } QWidget::mousePressEvent(e); }关键点e-pos()返回的是相对于Background1widget的坐标而m-getRect()也是基于同一坐标系无需转换。break保证单次点击只触发一次hit()防止快速连点导致分数虚高。若地鼠处于APPEARING或DISAPPEARING状态动画中getRect()可能还未覆盖洞口此时contains()返回false符合“只能击中完全出现的地鼠”的游戏规则。4. 避坑指南编译、资源、UI、逻辑四大高频翻车现场这份源码经过真实课程设计场景验证但新手照搬仍会踩坑。以下是我在实验室手把手调试时记录的5条血泪经验每条都按“现象→原因→解决”给出可操作方案。4.1 现象编译通过但启动黑屏控制台输出QPixmap: Cannot create a QPixmap from原因.qrc中注册的图片路径与实际文件名不一致或图片格式Qt不支持如WebP。源码包中starttt.png和startt.png易混淆.qrc只含startt.png但mainwindow.ui里可能引用了starttt.png。解决在Qt Creator中右键image.qrc→ “Open With” → “Qt Resource Editor”确认所有文件名拼写、大小写完全匹配检查mainwindow.uiXML源码搜索starttt.png将其全部替换为startt.png终极验证在main.cpp中加一行qDebug() QPixmap(:/images/startt.png).isNull();输出false才表示加载成功。4.2 现象按钮点击无反应connect()后信号从未触发原因自定义按钮类MyPushButton的mousePressEvent中未调用QPushButton::mousePressEvent(e)导致Qt内部clicked()信号无法发射。解决打开mypushbutton.cpp确认mousePressEvent和mouseReleaseEvent末尾均有QPushButton::xxx(e)调用。若使用Qt6还需在mypushbutton.h中添加Q_SIGNALS:宏声明信号Qt5可省略。4.3 现象地鼠出现位置固定总在左上角或超出窗口边界原因Mouse::appear()中随机坐标计算错误。源码中常见写法是x QRandomGenerator::global()-bounded(width());但width()返回的是Mouse对象自身宽度0而非父窗口宽度。解决在Mouse构造时传入父窗口指针或在appear()中通过QApplication::activeWindow()-width()获取。更稳妥做法是在Background1中生成地鼠时将窗口尺寸作为参数传入Mouse::appear(int w, int h)。4.4 现象游戏运行一段时间后崩溃报QObject: Cannot create children for a parent that is in a different thread原因Mouse对象在非GUI线程如QTimer的timeout槽中被new但未指定父对象。Qt要求所有QObject子类必须在GUI线程创建或明确指定父对象以自动管理内存。解决在Background1::updateGame()中创建地鼠时强制指定父对象Mouse *m new Mouse(this);。this是Background1属于GUI线程子对象自动归属其内存管理树。4.5 现象胜利界面youwin.h不显示或显示后立即关闭原因youwin类继承自QWidget但未调用show()或show()后未调用exec()阻塞。课程设计中常误用QDialog的exec()但QWidget需show()raise()activateWindow()三连。解决在Background1中连接胜利信号connect(this, Background1::gameWin, []() { YouWin *win new YouWin(this); // 父对象设为this避免野指针 win-show(); win-raise(); win-activateWindow(); });并在YouWin构造函数末尾加this-setModal(true);确保模态。5. 进阶技巧三步让打地鼠从“能跑”升级为“可交付课程设计作品”课程设计评分不止看功能更看重工程规范、可维护性和教学展示性。以下三个技巧是我带学生拿高分的实操方法每一步都对应源码可改的具体位置。5.1 技巧一用QSettings持久化存储最高分告别每次重启归零当前源码分数存在内存变量score中关闭程序即丢失。加入本地存储只需三步步骤1在Background1.h中添加头文件和成员#include QSettings // ... private: QSettings *settings; // 指向配置文件步骤2在Background1构造函数中初始化Background1::Background1(QWidget *parent) : QWidget(parent), settings(new QSettings(MyCompany, BeatMice)) { // 读取历史最高分 int bestScore settings-value(bestScore, 0).toInt(); qDebug() Loaded best score: bestScore; }步骤3在onMouseHit()中更新并保存void Background1::onMouseHit() { score 10; // ...原有逻辑 if (score settings-value(bestScore, 0).toInt()) { settings-setValue(bestScore, score); settings-sync(); // 立即写入磁盘 } }参数说明QSettings(MyCompany, BeatMice)在Windows下生成HKEY_CURRENT_USER\Software\MyCompany\BeatMice注册表项在Linux/macOS下生成~/.config/MyCompany/BeatMice.conf。sync()确保程序崩溃前数据不丢失。5.2 技巧二为地鼠添加音效用QSound实现零依赖播放Qt自带QSound类无需额外库即可播放WAV。源码中mouse.cpp可扩展步骤1在Mouse.h中添加音效成员#include QSound // ... private: QSound *hitSound;步骤2在Mouse构造函数中加载音效Mouse::Mouse(QObject *parent) : QObject(parent), hitSound(new QSound(:/sounds/hit.wav, this)) { // 注意WAV文件需提前放入.qrcprefix设为/sounds }步骤3在hit()中播放void Mouse::hit() { if (hitSound hitSound-isAvailable()) { hitSound-play(); // 异步播放不阻塞主线程 } state HIT; }注意QSound仅支持WAV格式且采样率需为44.1kHz、16位PCM。可用Audacity免费转换。.qrc中需新增qresource prefix/sounds节点。5.3 技巧三用QPainter动态绘制计时器数字替代静态图片当前倒计时用QLabel显示图片数字修改困难。改用矢量绘制支持任意字体、颜色、大小步骤1在Background1.h中声明绘图事件protected: void paintEvent(QPaintEvent *event) override;步骤2在Background1.cpp中实现void Background1::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, true); // 绘制倒计时数字居中显示 QFont font(Arial, 24, QFont::Bold); painter.setFont(font); painter.setPen(Qt::red); QString timeStr QString::number(timeLeft); QRect textRect fontMetrics().boundingRect(timeStr); QPoint center this-rect().center(); painter.drawText(center.x() - textRect.width()/2, center.y() textRect.height()/2, timeStr); QWidget::paintEvent(event); }优势数字样式完全可控无需准备10张数字图片timeLeft变量变更后重绘自动更新逻辑更内聚。从那以后我每次带课程设计都强制学生在git commit前走一遍这三步①用QSettings存最高分②给地鼠加QSound音效③把静态计时图换成QPainter绘制。不是为了炫技而是让学生亲手触摸到“软件工程”的毛细血管——配置管理、资源抽象、UI渲染分离。当他们第一次看到自己改的红色数字在屏幕上跳动听到“砰”的一声击中音效再点开注册表看到bestScore120那种“我造出来了”的笃定感比任何分数都真实。希望帮到你。本文还有配套的精品资源点击获取