QT C++宠物小精灵人机对战课设源码拆解:从C/S架构到答辩避坑
简介这套基于Qt与C开发的宠物小精灵人机对战游戏项目是面向计算机、通信、人工智能、自动化等专业学生及从业者的完整源码与文档资料也是答辩评分达98分的个人毕业设计项目适合作为期末课程设计、课程大作业或毕业设计参考也可供课余自学、项目实训使用。压缩包为zip格式共36个文件、容量1.88MB其中包含11个cpp与11个h源码文件、2个UI界面文件、3张png截图和qrc资源文件以及项目配置与说明文档目录按客户端与服务端模块划分结构清晰便于按需检索。项目采用客户端与服务端分离设计涵盖登录界面、精灵数据、玩家数据、对战逻辑和数据库等功能模块涉及Qt界面开发、图形绘制与数据库管理等常见技术点代码经过调试测试可正常运行并配有登录、战斗等界面截图和项目说明文档。目前已有99人浏览学习整体具有较好的学习借鉴价值既可用于课程设计与毕业设计参考也适合具备一定Qt与C基础的读者在此基础上继续修改、扩展新的玩法或界面效果。1. 基于QTC开发的宠物小精灵人机对战一份能跑到答辩现场的课设源码期末周最怕的不是写不出代码而是代码在自己机器上跑得好好的答辩投影一开就崩。这份基于QTC开发的宠物小精灵人机对战游戏项目就是那种代码经过调试测试、能直接跑起来的课设和毕设资源客户端负责界面交互服务端负责对战结算与存档登录、战斗、失败三个界面都有对应截图据说答辩评审做到了98分。适合正在纠结期末大作业选题的学生也适合想快速看懂Qt客户端/服务端结构和C继承设计的初级开发者。我拆完整个工程后最大的感受是它的问题不在代码量而在你知不知道先起服务端、再连客户端。下面按我的复现顺序把每一层讲清楚。2. 客户端与服务端的分工先看懂 problem3_client 和 problem3_server 再动手解压之后第一眼会被一堆文件名劝退.pro、.ui、.qrc、.autosave 混在一起。实际上整个工程在根目录下切成了两块problem3_server 是服务端工程problem3_client 是客户端工程。两个目录里都有 mainwindow、pokemon、player 等同名文件看起来像重复其实是故意做成的 C/S 双工程架构。2.1 从文件清单反推项目骨架拿到源码先别急着编译花十分钟对着文件清单做一次职责映射后面排错会快很多。我拆完之后的对应关系是这样的文件所在工程职责mainwindow.h/.cpp/.ui两端都有主窗口、信号槽连接、界面布局definition.h两端各一份公共枚举、宏、常量定义pokemonbase.h/.cpp两端各一份精灵基类种族值与属性结算pokemon.h/.cpp两端各一份具体精灵与技能表player.h/.cpp两端各一份玩家数据与操作请求database.h/.cpp仅服务端存档读写与图鉴数据初始化image.qrc / res.qrc仅客户端精灵贴图、背景、按钮资源problem3_client.pro / problem3_server.pro根目录qmake 工程文件为什么要拆成两个工程而不是一个 mainwindow 包打天下因为人机对战最怕 UI 卡一下、回合就乱套。服务端持有对战状态和数据库客户端只负责渲染和发指令比如把玩家按下的技能按钮翻译成一条带技能编号的消息发过去服务端结算完再把双方剩余血量发回来刷新界面。这样即使客户端窗口崩了服务端还能把这场对局的现场数据打出来。答辩的时候说我做了 C/S 分层 数据库持久化比说我写了一个大窗口值钱得多。2.2 回合状态机从 login 到 fight 再到 fail三个截图命名其实已经把主流程写出来了。Screenshot login.png 是登录窗口玩家输入名字后发给服务端校验校验通过进入 fight.png 的战斗界面左右两侧是玩家精灵和对手精灵中间是技能按钮和战斗日志区某一方血量归零后进入 fail.png 或胜利提示。整个对战过程是一个典型的状态机登录校验 → 队伍初始化 → 回合循环 → 胜负结算。服务端用循环推动客户端用窗口切换表现。// 服务端回合循环简化版收到的指令到结算结果的完整链路 bool Server::ProcessTurn(PlayerCommand cmd) { if (cmd.type CMD_SKILL) { int dmg CalcDamage(cmd.attacker, cmd.target, cmd.skillId); cmd.target.hp - dmg; // 先扣血 if (cmd.target.hp 0) { return EndBattle(cmd.target.side); // 返回胜负方 } int counter enemyAI.PickSkill(); // 敌方 AI 后手 CounterAttack(counter); // 走同样的结算流程 return CheckState(); // 玩家是否失败 } return false; }这里 PlayerCommand 结构体一般在 definition.h 里定义字段包括 side阵营、skillId技能编号、attacker 和 target 的精灵指针。skillId 对应精灵技能表的下标从 0 开始。如果你要扩展换精灵功能常见做法是加一个 cmd.type CMD_SWITCH 的分支把当前精灵换成队伍里血量最高的那只。客户端点技能按钮后通过 socket 把这条指令发给服务端服务端算完把双方血量广播回来。具体用的是 TCP 还是 UDP以 problem3_server.pro 里 QT network 后面挂的模块为准我拆到的这个版本走的是 TCP 长连接理由是回合制对战不允许丢包。3. 把项目跑起来Qt 环境搭配与两进程启动顺序代码本身能跑不算本事能在另一台刚装好 Qt 的机器上复现才算。这一章按我自己的复现顺序写先装环境再编服务端最后编客户端。顺序错了你会看到客户端登录按钮点了没反应而报错信息在服务端窗口里躺着。3.1 环境准备Qt 版本、编译器套件和工作目录建议装 Qt 5.12.12 或 5.15.2这两个 LTS 版本对课设源码兼容性最好。下载安装的时候勾选一个 MinGW 32 位或 64 位套件即可不要同时勾 MSVC 再来回切。为什么不用 Qt 6这份源码里不少写法是 Qt 5 风格的比如旧的信号槽语法和资源路径处理用 6.x 编译会冒出一堆弃用警告处理起来纯属浪费时间。另外两点一是两个工程必须用同一套件编译混着用会在链接阶段报一堆幺蛾子二是整个目录放到纯英文路径下Qt 对中文路径的支持时好时坏没必要赌。如果你非要用 VS Code 配 CMake 编译而不是 Qt Creator核心矛盾在编译器路径匹配qmake 生成的 Makefile 里写死了套件路径换 IDE 不如直接接受 Qt Creator 的工程文件来得省心。3.2 构建顺序先 server 后 client一个窗口一个坑打开 Qt Creator先加载 problem3_server.pro直接点构建运行。服务端窗口弹出来之后会进入监听状态常见实现是在 mainwindow 构造函数里绑定本地端口并开始 listen。然后另开一个 Qt Creator 实例加载 problem3_client.pro同样构建运行。两个窗口并存时一定是服务端先就绪。# 也可以在命令行下编服务端Windows 进入 MinGW 环境后 mkdir build-server cd build-server qmake ..\problem3_server\problem3_server.pro mingw32-make -j4 # 产物在 build-server\release\ 下先运行 server.exe再运行 client.exeqmake 的作用是根据 .pro 文件生成 Makefilemingw32-make 才真正执行编译和链接。-j4 表示四个编译任务并行课设机器四核足够用。如果你在 Qt Creator 里点绿色三角等价操作是自动帮你做这两步。唯一的差别是影子构建目录Creator 默认把中间文件放到 build-xxx 目录命令行手动 qmake 时要注意当前目录别和 .pro 所在目录搞混。3.3 连不上服务端时的三板斧客户端登录没反应最常见的三个原因分别是服务端没先启动、端口被上次残留进程占用、防火墙弹窗被点了拒绝。排查手段按顺序来。// 服务端 main.cpp 启动阶段一般会有这类调试输出 qDebug() server listening on port serverPort;看到这行输出再操作客户端否则 Client 里的 connectToHost 会一直重试表现为按钮点了没反应。端口占用可以用系统命令查Windows 下 netstat -ano | findstr 9999看到 TIME_WAIT 或 LISTENING 就说明有残留进程去任务管理器结束掉再重启服务端。本地回环地址一般不需要防火墙放行但杀毒软件可能会拦 exe 的网络访问弹窗时直接允许即可。4. 对战核心逻辑拆解属性克制、伤害公式与 C 随机数答辩时老师十有八九会往伤害结算和 AI 决策上问这是整个项目里最有含金量的部分。理解 pokemonbase 和 pokemon 的继承关系再把随机数种子讲清楚基本就能应付过去。4.1 pokemonbase 与 pokemon种族值和技能的继承设计pokemonbase.h 是基类存放精灵的种族值和通用战斗方法pokemon.h 是派生类在基类基础上挂技能表和属性。伤害计算、扣血、死亡判断都写在基类里派生类只需要在构造函数里把自己的数值填进去。// definition.h 中的属性枚举两个工程必须保持一致 enum Attr { ATTR_FIRE, ATTR_WATER, ATTR_GRASS, ATTR_ELECTRIC }; // pokemonbase.h 基类种族值 当前状态 通用战斗方法 class PokemonBase { protected: int baseHp, baseAtk, baseDef, baseSpeed; // 种族值 Attr attr; // 精灵属性 public: virtual int CalcDamage(PokemonBase target, int skillIdx); void TakeDamage(int dmg) { hp - dmg; } bool IsDead() const { return hp 0; } private: int hp; // 当前血量初始等于 baseHp };这种设计的妙处在于新增一只精灵不需要改战斗逻辑只需要在 database.cpp 的初始化表里加一行数据然后 new 一个 pokemon 对象塞给玩家或敌人。数据库文件里存的其实就是这些数值的快照服务端启动时读库战斗结束时回写存档思路和成绩管理系统是同一套。4.2 伤害公式里的随机数种子别让两次攻击打出一样的伤害常见的伤害公式是伤害 技能威力 x攻击 / 防御x 属性倍率 x 随机浮动。前面几项都是定值唯一的不确定性来自随机浮动。这里有个答辩经典送命题如果源码用的是 rand()又没有主动设置种子两次运行同一场战斗会打出完全一样的伤害序列老师一旦发现就会追问。// 用 C11 的随机数代替 rand()保证每次对局伤害序列不同 #include random static std::mt19937 rng(std::random_device{}()); float RandFactor() { std::uniform_real_distributionfloat dist(0.85f, 1.0f); return dist(rng); // 每次调用产生 0.85 ~ 1.0 的浮动系数 }std::random_device 负责取真随机种子mt19937 是梅森旋转算法生成质量比线性同余的 rand() 好一个档次。uniform_real_distribution 把输出限制在 0.85 到 1.0 之间这个区间就是所谓的伤害浮动范围。取值范围别乱改改成 0.5 会导致低血量精灵完全无法反杀对局平衡直接崩。我在检查时发现源码里如果有 qrand() 或者全局 srand(time(0)) 也是可以接受的方案关键是有没有主动初始化种子。血条绘制这一块也在 mainwindow.cpp 里用 QPainter 在 paintEvent 里画两个矩形底灰面红宽度按 hp 比例缩放。这是 Qt 绘图里最基础的用法改成渐变或者加个 QPropertyAnimation 动画都是加分项。4.3 人机 AI 的两种常见策略AI 不是状态机不需要多聪明但至少要让人觉得它在针对我。最常见的是贪心策略遍历自己的技能表按克制关系和基础威力算一遍打出去的伤害预估选最高的打。// 贪心选技能预估每个技能对目标的伤害取最大值 int EnemyAI::ChooseSkill(PokemonBase self, PokemonBase target) { int best 0; for (int i 0; i self.SkillCount(); i) { int a self.EstimateDamage(i, target); int b self.EstimateDamage(best, target); if (a b) { best i; // 谁预估值高就选谁 } } return best; }进阶一点的做法是给 AI 加一个状态判断当自己血量低于 30% 时优先切换到属性克制对手的候补精灵而不是继续硬刚。这个判断放在回合循环的最前面和玩家指令一起进 ProcessTurn。答辩时能说出AI 有两种策略血量健康时贪心输出残血时换人这种话比你背十个类名都有说服力。5. 避坑手册复现这份课设时容易翻车的五个点以下五条全部来自我实际复现时的血泪记录每一条的报错信息都原样保留方便你对照搜索引擎。5.1 编译阶段的两个高频报错1. 现象编译或运行时弹出 fatal: cannot mix incompatible qt library (version 0x50601) with this library。原因version 0x50601 解析成十进制就是 Qt 5.6.1。说明工程自己带的动态库或者环境变量里的 Qt 路径和你当前构建使用的 Qt 版本串了。常见触发方式是系统 PATH 里残留了旧版本 Qt 的 bin 目录或者从别的机器拷过来的 build 目录没清理。解决在 Qt Creator 里把影子构建目录整个删掉重新 qmake 再编译命令行用户检查环境变量把 PATH 里多余的 qt 路径去掉。两个工程统一走同一套件。2. 现象qt.qpa.plugin: could not find the Qt platform plugin linuxfb。原因这个报错常见于在无桌面环境的 Linux 或嵌入式板子上运行 GUI 程序Qt 找不到了 linuxfb 平台插件。Windows 上如果出现类似报错多半是构建套件选成了嵌入式版或者安装 Qt 时只勾了源码没勾 platform plugin。解决源码本身没问题。Windows 下重新安装桌面版 Qt 套件Linux 下补装 libqt5gui5 和相关平台插件目录再确认 QPA_PLATFORM_PLUGIN_PATH 环境变量指向 Qt 安装目录下的 plugins/platforms。5.2 运行阶段的三个问题3. 现象服务端能启动但 database.cpp 不生成存档文件或者登录时读不到玩家数据。原因数据库用的相对路径而程序当前工作目录不在 exe 所在目录。Qt Creator 默认的工作目录是工程根目录和 release 产物目录经常不一致。解决在 Qt Creator 的项目 - 运行 - 工作目录里改成 release 输出目录或者全局搜 DATABASE_PATH改成写死的绝对路径。我一般直接改绝对路径课设脚本发给别人时省得解释。4. 现象客户端登录按钮点了没反应服务端窗口也没有任何日志输出。原因客户端先于服务端启动socket 连接失败后进入静默重试或者上一个 server.exe 没退干净端口还被占着。这两个原因会同时存在先启动的客户端一直连不上后启动的服务端端口又被占。解决先到任务管理器结束所有 server.exe 和 client.exe 进程然后严格按先 server 后 client 的顺序启动。修改代码后重新构建别忘了旧进程还开着这是 Qt 课设里最容易犯的一个低级失误。5. 现象换一台电脑解压后打开 .pro 提示套件过期编译报一堆 undefined reference。原因.pro.user 文件记录的是原作者机器上的构建套件路径和编译器配置换机器后这些路径全失效。解决直接把 .pro.user 删除让 Qt Creator 重新解析 .pro 并自动选择套件。另外 res.qrc.autosave 是 Qt Designer 自动保存的备份文件不影响编译可以一并清理。6. 改造成你的大作业换皮、加精灵、发布 exe 一条龙这份源码最适合当骨架用完成度越高越适合动手改。换皮加精灵都不需要动核心战斗逻辑按下面三步走一晚上能改完。换皮不动代码打开 res.qrc里面列出的是相对路径的图片资源。把同名新图丢进对应目录保持文件名完全一致CtrlS 保存资源文件后重新编译即可。注意图片名大小写敏感 login.PNG 和 login.png 在资源表里是两个文件。血条颜色和按钮样式在 mainwindow.cpp 的 setStyleSheet 里改色值就行。加一只新精灵在 database.cpp 的图鉴初始化表里复制一条现有精灵的数据行改名字、种族值和技能编号然后再在 pokemon.cpp 的构造函数里挂上对应的技能。新增一行数据的示例是这样的-- 往精灵图鉴表插入一条字段名以 database.cpp 建表语句为准 INSERT INTO pokemon (id, name, attr, base_hp, base_atk, base_def, skill_ids) VALUES (25, 皮卡丘, 3, 35, 55, 40, 1,2,5,8);skill_ids 是技能表下标的逗号分隔串新增技能时要先在技能表里把技能本身定义好再回来引用。别的什么都不用动继承和多态会帮你把伤害计算跑通。发布与答辩回归清单用 release 模式重新编译然后把生成的 exe 拖到 windeployqt 上它会自动把需要的 Qt 动态库和插件补齐这是 Qt 发布软件的标准做法。答辩前一天强制走一遍清空 .pro.user → 统一套件 → 先服务端后客户端 → 打满一整局 → 分别验证胜利和失败两个分支 → 在 CalcDamage 上打断点确认随机数落在 0.85 到 1.0 之间。从那以后我每次拿到 Qt 课设源码都强制走一遍这条路先删 .pro.user再统一套件然后先服务端后客户端启动最后打满完整回合看结算输出。这套流程帮我避开了至少三次答辩现场翻车。这份源码最值钱的不是那个 98 分的评分而是 C/S 分层和继承多态这两块骨架改坏了直接从压缩包重新解压一份继续折腾希望帮到你。本文还有配套的精品资源点击获取