架构概述
本指南适用于拥有完整 MCE C# 源代码访问权限的 Source 版本用户。
本文档涵盖模块层级结构、程序集结构、依赖注入模式以及构成 Monster Capture Engine 的关键运行时系统。
模块层级结构
MCE 按照分层架构组织模块:
Assets/
OpenMon/
Core/
Runtime/ # 核心引擎(必需)
Initialization/ # 引擎启动、首次向导
Monster/ # MonsterInstance、Roster、进化、繁殖
MonsterDatabase/ # ScriptableObject 定义(物种、招式、道具、特性)
Battle/ # BattleManager、15+ 模块、状态机、AI
Characters/ # MCECharacterController、PlayerCharacter、跟随者
Actors/ # Actor 系统、CommandGraph 可视化脚本
World/ # GridController、GlobalGridManager、地块、遭遇
Saves/ # SavegameManager、SavableObject、GameVariables
Player/ # GlobalGameData、玩家状态
UI/ # 所有 UI 控制器和视图
Quests/ # 任务系统(目标、奖励、追踪)
SDK/ # 公共 API 接口
GameFlow/ # 游戏状态管理
Configuration/ # ScriptableObject 配置
Rendering/ # URP 着色器、昼夜系统、天气
Audio/ # AudioManager、BGM/SE/ME
Localization/ # 多语言支持
Badges/ # 徽章追踪
Input/ # 输入抽象
MonsterDex/ # 图鉴追踪(已见/已捕获)
Animation/ # 动画工具
Shaders/ # 着色器源文件
Editor/ # 仅编辑器工具
ArtGenerator/ # AI 精灵图生成
MonsterCreator/ # 怪兽创建向导
Tools/ # 数据库浏览器、属性表编辑器、验证器
Followers/ # 跟随者精灵表工具
Actors/ # CommandGraph 编辑器
Callbacks/ # 编辑器回调工具
依赖规则
- 核心运行时对 Online 或 Importer 模块零外部依赖。
- 在线功能通过
MCE_ONLINE脚本定义符号可选启用。 - Editor 代码通过
.asmdef边界严格与 Runtime 分离。 Runtime/SDK/中的 SDK 接口是 DLL 版本用户的公共 API 表面。
程序集结构
MCE 使用 Unity 程序集定义来强制编译边界:
| 程序集 | 位置 | 用途 | 引用 |
|---|---|---|---|
OpenMon.MCE.Runtime | Core/Runtime/ | 核心游戏系统 | 仅基础库 |
OpenMon.MCE.Editor | Core/Editor/ | 编辑器工具 | Runtime + UnityEditor |
MCE.Online.Runtime | MCE_Online/Runtime/ | Nakama 网络 | Runtime + Nakama SDK |
MCE.Online.Editor | MCE_Online/Editor/ | 在线编辑器工具 | Online Runtime + UnityEditor |
MCE.EssentialsImporter.Editor | MCE_EssentialsImporter/Editor/ | 导入管线 | Runtime + KaitaiStruct |
MCE.Runtime.Tests | Core/Tests/Runtime/ | 核心测试 | Runtime + NUnit |
为什么程序集定义很重要
- 编译隔离:Editor 代码的更改不会重新编译 Runtime。
- 依赖强制:编译器阻止 Runtime 引用 Editor API。
- 构建裁剪:Editor 程序集在玩家构建中被排除。
- 测试隔离:测试程序集可以引用内部成员而不在生产中暴露。
依赖注入(Zenject)
MCE 使用 Zenject(Extenject)配合安装器模式进行依赖注入。
安装器模式
每个子系统有自己的安装器来注册绑定:
public class BattleLauncherInstaller : MonoInstaller
{
[SerializeField] private BattleLauncher battleLauncherPrefab;
public override void InstallBindings()
{
Container.Bind<IBattleLauncher>()
.To<BattleLauncher>()
.FromComponentInNewPrefab(battleLauncherPrefab)
.AsSingle()
.Lazy();
}
}
关键安装器
| 安装器 | 绑定内容 |
|---|---|
GameFlowInstaller | 核心游戏流程服务 |
PlayerCharacterInstaller | PlayerCharacter、角色服务 |
BattleLauncherInstaller | BattleLauncher、战斗入口 |
GridInstaller | GridController、地块数据 |
SceneInfoInstaller | SceneInfo、场景元数据 |
WorldDatabaseInstaller | WorldDatabase、全局地图数据 |
TileDataInstaller | 地块类型数据 |
GlobalGridManagerInstaller | 跨地图导航 |
SavegameInstaller | SavegameManager、序列化 |
EvolutionManagerInstaller | EvolutionManager |
约定
- 绑定按约定使用
.AsSingle().Lazy()(单实例,首次使用时创建)。 [Inject]用于Construct()方法,而非构造函数。DialogManager.Factory模式用于运行时 UI 实例化。- 场景作用域安装器处理每场景服务。
- 项目作用域安装器(在
ProjectContext中)处理全局服务。
注入点
public class SomeManager : MonoBehaviour
{
// 字段注入(用于 MonoBehaviour)
[Inject] private IMonsterDatabase database;
// 方法注入(推荐用于显式依赖)
[Inject]
public void Construct(IBattleSystem battle, IPlayerData player)
{
this.battle = battle;
this.player = player;
}
}
关键运行时系统
MCEInitialization 流程
MCEInitialization 是游戏启动时首先运行的内容:
1. 屏幕设置(分辨率、方向)
2. 加载数据库(怪兽、招式、道具、特性)
3. 下载本地化数据(如果是远程的)
4. 运行首次向导(如果需要)
5. 初始化子系统(存档管理器、音频、输入)
6. 发出就绪信号
7. 加载主菜单或继续存档游戏
这是持久化 GameObject 上的一个 MonoBehaviour,在场景加载时不被销毁。
怪兽系统
怪兽数据模型:
MonsterEntry(ScriptableObject - 物种定义)
├── 基础能力值、属性、特性
├── DataByFormEntry[](每形态覆盖)
├── EvolutionData[](进化路径)
└── 招式列表(升级、TM、蛋、教授)
MonsterInstance(运行时 - 个体怪兽)
├── 物种引用(MonsterEntry)
├── 等级、经验值
├── IVs[6]、EVs[6]
├── 性格
├── 当前 HP、状态
├── MoveSlot[4](含 PP)
├── 亲密度、初训数据
└── 形态索引
Roster(最多 6 只 MonsterInstance 的队伍)
MonsterStorage(PC 盒子)
战斗系统
请参阅战斗内部机制获取详细解析。
世界系统
GlobalGridManager(跨地图导航)
└── GridController(每场景网格)
├── TileData 网格(碰撞、每地块类型)
├── EncounterTile 区域
└── SceneInfo(元数据)
MCECharacterController(移动)
├── PlayerCharacter(输入 + 遭遇)
└── ActorCharacter(NPC AI 移动)
存档系统
SavegameManager
├── MCESavesSerializer(JSON 读写)
├── SavableObject[](已注册的可存档对象)
└── GameVariables(全局状态)
代码风格
代码库遵循以下约定:
- 4 空格缩进,大括号另起一行。
- PascalCase 用于公共类型、方法、属性。
- camelCase 用于私有字段和局部变量。
[SerializeField] private优先于公共字段。Runtime/和Editor/通过.asmdef边界分离。- 约定式提交:
feat:、fix:、docs:、refactor:。
序列化字段
不要在没有迁移计划的情况下重命名序列化字段。场景和预制体依赖于序列化字段名称。重命名时使用 [FormerlySerializedAs("oldName")]。
扩展引擎
添加新系统
- 在
Runtime/YourSystem/中创建你的类。 - 为依赖注入创建安装器。
- 将安装器添加到适当的场景上下文或项目上下文。
- 如果需要存档数据,扩展
SavableObject。 - 如果有编辑器工具,在
Editor/YourSystem/中创建类。
修改现有系统
- 阅读现有代码并理解模块的依赖关系。
- 检查更改是否可以通过配置或扩展完成,而非修改。
- 如果要修改,遵循模块中的现有模式。
- 更新测试以覆盖更改。
- 在提交消息中记录更改。
性能注意事项
- 数据库查找按图鉴编号为 O(1),按名称为 O(n)。如果频繁调用,请缓存名称查找。
- 战斗模块每场战斗初始化一次,而非每回合。模块创建不是热路径。
- Tilemap 在约 200x200 地块时有性能上限。拆分更大的地图。
- 存档序列化在主线程上同步执行。保持存档数据大小合理。
- CommandGraph 执行基于协程。包含许多节点的复杂图表适合事件脚本,但不应用于每帧逻辑。