简介:这是一份面向C++游戏开发学习者与课程设计需求者的农场生活模拟游戏项目源码,采用Cocos引擎与CMake构建,适合作为程序设计范式期末大作业的完整参考。项目实现类星露谷玩法,涵盖农场管理中的耕种、浇水、施肥与作物病害处理,养殖牛、鸡、羊等动物,社区交互中的居民关系、节日活动与委托任务,以及森林、山脉、洞穴探索和采矿、钓鱼、技能树成长等系统。压缩包共约2000个文件,以1230个h头文件与623个cpp源文件为核心,另有44个c、34个java及少量xml、json、py、sh等配置与脚本文件,整体约249.21MB,目录结构便于按模块查阅。资源附有开发日志,记录了Cocos与CMake环境配置及小组分工等起步环节,已有157人学习。读者可借此理解大型C++项目的组织方式、引擎集成流程与游戏系统分层设计,用于课程作业参考或二次开发练手。
1. 从零搭一个类星露谷的 C++ 农场模拟:为什么环境配置才是期末大作业的第一道坎
很多人做程序设计范式期末大作业,第一反应是打开编辑器写Player类和Crop类,结果卡在编译报错上一整周。我见过太多类似的翻车现场:代码逻辑想得挺清楚,CMake 一跑满屏红字,Cocos 的库路径找不到,链接器报一堆 undefined reference,最后交作业前两天才开始慌。这篇笔记就围绕「基于 C++ 的农场生活模拟游戏」这个方向,把从环境配置到核心系统落地讲透。它适合正在做类星露谷玩法大作业、想用 C++ 和 Cocos 走完整条链路的人。读完你能拿到一套可复现的 CMake 配置、几个核心系统的设计思路,以及我踩过的坑。
2. 先把技术选型定死:C++、Cocos 与 CMake 各自负责什么
2.1 为什么是 C++ 而不是脚本语言
农场模拟这类游戏的核心循环是「时间推进 → 作物状态更新 → 玩家交互 → 渲染刷新」。用 C++ 写,最大的好处是你能把程序设计范式课上的东西真正用起来:面向对象、继承与多态、模板、RAII、设计模式,全都有落点。比如作物可以抽象成基类,不同作物用派生类实现生长曲线;物品栏可以用模板容器;资源管理用 RAII 保证不泄漏。
脚本语言当然也能做,但期末大作业的评分点往往就在「你有没有用上课上讲过的范式」。C++ 天然适合展示这些。代价是环境配置更麻烦,编译链接的坑更多,这也是为什么第 3 章要花大篇幅讲 CMake。
2.2 Cocos 在项目里承担的角色
Cocos 在这里主要干三件事:窗口与渲染循环、精灵与动画、输入事件分发。你不需要自己写 OpenGL 或 DirectX,Cocos 把底层封装好了。农场游戏里的地块、作物、角色、UI 面板,都可以用Sprite和Node组织成场景树。
常见做法是把游戏逻辑和渲染分离:逻辑层用纯 C++ 写,不依赖 Cocos 的类型;渲染层再把这些逻辑对象映射成 Cocos 的节点。这样做的好处是逻辑层可以单独写单元测试,不用起图形界面。我一般会建两个目录:src/core放纯逻辑,src/render放 Cocos 相关代码。
2.3 CMake 为什么不能省
有人觉得直接用 IDE 建工程就行,何必折腾 CMake。问题是 Cocos 本身依赖一堆第三方库,你的项目又要跨平台,IDE 工程文件一旦换机器就废了。CMake 的价值在于把「依赖什么、编译哪些源文件、链接哪些库」写成可移植的脚本,换平台只改工具链文件。
而且程序设计范式课往往要求你展示工程组织能力,一个干净的CMakeLists.txt比一堆 IDE 配置文件更能说明问题。下面这张表是我建议的目录结构:
| 目录 | 职责 | 是否依赖 Cocos |
|---|---|---|
| src/core | 游戏逻辑、数据模型 | 否 |
| src/render | 场景、精灵、UI | 是 |
| src/utils | 工具函数、配置读取 | 否 |
| assets | 图片、音频、字体 | 否 |
| tests | 逻辑层单元测试 | 否 |
| cmake | 自定义查找模块 | 否 |
3. 用 CMake 把 Cocos 和你的代码接起来:一份能跑通的最小配置
3.1 顶层 CMakeLists 的骨架
先给一份我实际用过的顶层配置,重点是找到 Cocos 的安装路径并把它的库引进来。不同版本的 Cocos 目录结构略有差异,下面用变量COCOS2DX_ROOT统一管理。
cmake_minimum_required(VERSION 3.16) project(FarmLifeSim LANGUAGES CXX) # 统一用 C++17,模板和文件系统都用得上 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 允许外部通过 -DCOCOS2DX_ROOT=... 指定引擎路径 if(NOT DEFINED COCOS2DX_ROOT) message(FATAL_ERROR "请通过 -DCOCOS2DX_ROOT=路径 指定 Cocos 根目录") endif() # 把引擎的 cmake 模块目录加入搜索路径 list(APPEND CMAKE_MODULE_PATH ${COCOS2DX_ROOT}/cmake) # 引入引擎提供的构建脚本,具体文件名以你的版本为准 include(${COCOS2DX_ROOT}/cmake/CocosBuildSet.cmake) # 逻辑层:纯 C++,不链接引擎 add_library(farm_core STATIC src/core/GameClock.cpp src/core/Crop.cpp src/core/Inventory.cpp src/core/WorldMap.cpp ) target_include_directories(farm_core PUBLIC src) # 渲染层与可执行文件:链接引擎 add_executable(farm_game src/main.cpp src/render/FarmScene.cpp src/render/CropNode.cpp ) target_link_libraries(farm_game PRIVATE farm_core cocos2d)逻辑说明:farm_core被编成静态库,好处是测试目标也能链接它,不用重复编译。target_include_directories用PUBLIC让依赖它的目标自动获得头文件路径。farm_game才链接cocos2d,保证逻辑层不被引擎污染。
参数说明:COCOS2DX_ROOT必须由命令行传入,避免硬编码路径。CMAKE_CXX_STANDARD设成 17,因为std::filesystem读配置表很方便。CocosBuildSet.cmake这个文件名在不同版本里可能不同,你需要去引擎的cmake目录下确认实际名字。
3.2 配置与构建的具体命令
配置阶段最容易出错,建议单独建一个build目录,保持源码目录干净。
# 在项目根目录执行 cmake -S . -B build \ -DCOCOS2DX_ROOT=/path/to/your/cocos \ -DCMAKE_BUILD_TYPE=Debug # 构建,-j 后面跟并行任务数 cmake --build build -j 8逻辑说明:-S .指定源码目录,-B build指定构建目录,这种写法比进目录再cmake ..更清晰。CMAKE_BUILD_TYPE在 Debug 下会带调试符号,方便你定位崩溃。
参数说明:-j 8按你机器的核数调整,核多就调大。如果链接阶段报内存不足,把并行数降下来。Debug 构建体积大但排查问题方便,发布时换成Release。
3.3 让逻辑层脱离引擎单独编译
这是我最推荐的一个习惯:给farm_core单独写一个测试目标,不依赖 Cocos,这样编译快、跑得快。
# 追加在顶层 CMakeLists 末尾 enable_testing() add_executable(farm_tests tests/test_crop.cpp tests/test_inventory.cpp ) target_link_libraries(farm_tests PRIVATE farm_core) add_test(NAME farm_tests COMMAND farm_tests)逻辑说明:farm_tests只链接farm_core,完全不碰引擎,所以哪怕 Cocos 环境没配好,逻辑层的测试照样能跑。这在赶作业时特别有用,渲染出问题不影响你验证核心算法。
参数说明:enable_testing()打开 CTest 支持,add_test注册测试用例,之后用ctest --test-dir build就能跑。测试框架可以自己写简单的断言宏,也可以引入轻量库,看课程要求。
4. 农场核心系统怎么落地:时间、作物与背包
4.1 游戏时钟:把「一天」拆成可推进的刻度
农场游戏的时间系统是整个玩法的发动机。作物生长、商店刷新、NPC 作息都挂在它上面。我的做法是把时间抽象成一个纯逻辑类,内部只维护「当前第几天、当前第几分钟」,渲染层每帧读它。
// src/core/GameClock.h #pragma once class GameClock { public: // 一天按 24 小时 * 60 分钟计,每 tick 推进若干分钟 explicit GameClock(int minutesPerTick = 10) : minutesPerTick_(minutesPerTick) {} void Tick() { minuteOfDay_ += minutesPerTick_; while (minuteOfDay_ >= 24 * 60) { minuteOfDay_ -= 24 * 60; ++day_; } } int Day() const { return day_; } int Hour() const { return minuteOfDay_ / 60; } int Minute() const { return minuteOfDay_ % 60; } private: int day_ = 1; int minuteOfDay_ = 6 * 60; // 从早上 6 点开始 int minutesPerTick_; };逻辑说明:Tick里用while而不是if,是为了防止minutesPerTick_设得过大时一次跨过多天。时间从早上 6 点起,符合农场游戏的常见设定。
参数说明:minutesPerTick_决定游戏节奏,设 10 表示每次逻辑更新推进 10 分钟,一天约 144 次更新。设太小节奏拖沓,设太大作物一夜就熟,失去经营感。我一般先用 10 调试,手感不对再调。
4.2 作物生长:用状态机而不是一堆 if
作物从种子到成熟,中间有若干阶段。新手容易写成一长串if (day > X && day < Y),改起来痛苦。用状态机清晰得多。
// src/core/Crop.h #pragma once #include <string> enum class CropStage { Seed, Sprout, Growing, Mature }; class Crop { public: Crop(std::string id, int daysToMature) : id_(std::move(id)), daysToMature_(daysToMature) {} // 每天调用一次,推进生长 void OnNewDay() { if (stage_ == CropStage::Mature) return; ++ageDays_; // 按成熟天数三等分,映射到三个阶段 if (ageDays_ >= daysToMature_) { stage_ = CropStage::Mature; } else if (ageDays_ * 3 >= daysToMature_ * 2) { stage_ = CropStage::Growing; } else if (ageDays_ * 3 >= daysToMature_) { stage_ = CropStage::Sprout; } } bool IsHarvestable() const { return stage_ == CropStage::Mature; } CropStage Stage() const { return stage_; } const std::string& Id() const { return id_; } private: std::string id_; int daysToMature_; int ageDays_ = 0; CropStage stage_ = CropStage::Seed; };逻辑说明:用整数乘法代替浮点除法做阶段判断,避免精度问题。OnNewDay由时钟的「跨天事件」触发,而不是每帧调用,保证逻辑清晰。
参数说明:daysToMature是核心平衡参数。叶菜类设 3 到 5 天,果树类设 10 天以上。阶段划分比例可以改,比如想让「Growing」阶段更长,就调整那两个阈值。
4.3 背包系统:模板容器加物品元数据
背包要能放种子、作物、工具,类型不同但都要能堆叠、能查询。我用一个模板化的槽位结构,配合物品元数据表。
// src/core/Inventory.h #pragma once #include <unordered_map> #include <string> #include <vector> struct ItemStack { std::string itemId; int count = 0; }; class Inventory { public: explicit Inventory(int capacity) : slots_(capacity) {} // 返回实际放入的数量,放不下就返回剩余 int AddItem(const std::string& id, int amount) { for (auto& slot : slots_) { if (slot.itemId == id && slot.count > 0) { slot.count += amount; return 0; } } for (auto& slot : slots_) { if (slot.count == 0) { slot.itemId = id; slot.count = amount; return 0; } } return amount; // 没有空位 } const std::vector<ItemStack>& Slots() const { return slots_; } private: std::vector<ItemStack> slots_; };逻辑说明:先找同类物品堆叠,再找空位。返回剩余数量而不是布尔值,调用方可以据此提示「背包已满」。
参数说明:capacity是槽位数,常见设 24 或 36。如果要做「堆叠上限」,在AddItem里加一个maxStack判断即可,注意处理跨槽分配。
5. 避坑与排查:环境配置和逻辑实现里最容易翻车的地方
5.1 链接报 undefined reference 到 Cocos 符号
现象:编译通过,链接阶段报一堆undefined reference to cocos2d::...。
原因:target_link_libraries里没写cocos2d,或者引擎库路径没被正确加入。也有可能是 Debug 和 Release 库混用。
解决:确认farm_game链接了cocos2d,检查COCOS2DX_ROOT指向的目录里确实有对应构建类型的库文件。Debug 构建就链接 Debug 库,别混。
5.2 头文件找得到但源文件没编进去
现象:报某个类的成员函数未定义,但头文件明明 include 了。
原因:add_library或add_executable的源文件列表里漏了对应的.cpp。
解决:养成习惯,新增.cpp后立刻在 CMake 里登记。可以用file(GLOB ...)自动收集,但我不推荐,因为新增文件后 CMake 不一定重新配置,容易出玄学问题。手动列更稳。
5.3 时间推进导致作物瞬间成熟
现象:调试时作物一帧就熟了。
原因:把Tick或OnNewDay放进了每帧更新的update里,而帧率远高于预期。
解决:逻辑更新和渲染更新分离。用一个固定步长累加器,只有累计时间超过阈值才推进一次逻辑。Cocos 的update(float dt)里做累加,别直接调Tick。
5.4 背包添加物品后界面不刷新
现象:数据改了,UI 没变。
原因:逻辑层和渲染层没有同步机制,渲染层只在初始化时读了一次数据。
解决:加一个简单的观察者或事件回调,背包变更时通知 UI 重建。别每帧全量刷新,浪费性能。我一般用一个dirty标志,UI 在update里检查标志再刷新。
5.5 换机器后 CMake 配置失败
现象:在自己电脑上好好的,换台机器就报找不到引擎。
原因:COCOS2DX_ROOT用了绝对路径且写死在脚本里,或者依赖了本机特有的环境变量。
解决:永远通过命令行传路径,脚本里只做校验。把配置命令写进一个build.sh或文档里,换机器照着跑。
6. 进阶技巧:让大作业从「能跑」变成「能讲」
到这一步,游戏能跑起来了,但期末大作业的评分往往还看「设计范式用得对不对」。我分享几个让代码更有说服力的做法。
第一个是给作物系统加一个简单的工厂。不同作物的创建参数从配置表读,工厂负责根据 id 返回对应的Crop实例。这样新增作物不用改核心代码,符合开闭原则。配置表用简单的文本格式就行,逻辑层用std::ifstream读,别引入重型依赖。
第二个是把「跨天事件」做成一个轻量的发布订阅。时钟跨天时发一个事件,作物、商店、NPC 各自订阅。这样时钟不需要知道谁在监听,耦合度低。实现上用一个std::vector<std::function<void()>>就够,不必上复杂的框架。
第三个是给逻辑层补测试。前面配的farm_tests目标就是干这个的。写几个用例:作物在成熟天数后变成Mature、背包满了返回剩余数量、时钟跨天时天数加一。这些用例跑起来只要几毫秒,但能帮你在改代码时快速发现回归。答辩时展示测试通过,比口头说「我测过了」有说服力得多。
验证方法上,我习惯用一个「加速模式」:把minutesPerTick_临时设成 720,一天两次 tick 就过完,快速验证多天后的作物状态和商店刷新。这个开关只在 Debug 下生效,发布时关掉。
最后一个具体技巧:把游戏常量集中到一个头文件里,用constexpr定义。比如成熟天数、背包容量、初始金钱。这样调平衡时只改一个地方,不用满项目搜魔法数字。我吃过这个亏,早期把数字散在各处,后来想调节奏改了十几处还漏了一个,导致测试数据和实际表现对不上,排查了半天。
这些做法不复杂,但能让你的大作业在「工程性」这一项上明显高出一截。我自己的习惯是每写完一个系统,先问自己「如果老师让我加一种新作物,我要改几个文件」,答案超过两个就说明抽象还不够。希望帮到你。
本文还有配套的精品资源,点击获取