跳到主要内容

架构概览

本指南面向可以访问完整 MCE C# 源码的 Source 和 Studio 版用户。

模块架构

箭头有意只指向一个方向。Core 绝不依赖 Online 或导入器, 这正是让在线层成为真正可选,而不仅仅是在 README 里可选的原因。

本文档涵盖模块层级、程序集结构、依赖注入模式,以及构成 Monster Capture Engine 的关键运行时系统。

模块层级​

MCE 被组织为遵循分层架构的模块:

Assets/
OpenMon/
Core/
Runtime/ # Core engine (REQUIRED)
Initialization/ # Engine startup, first-time wizard
Monster/ # MonsterInstance, Roster, evolution, breeding
MonsterDatabase/ # ScriptableObject definitions (species, moves, items, abilities)
Battle/ # BattleManager, 15 modules, state machine, AI
Characters/ # MCECharacterController, PlayerCharacter, followers
Actors/ # Actor system, CommandGraph visual scripting
World/ # GridController, GlobalGridManager, tiles, encounters
Saves/ # SavegameManager, SavableObject, GameVariables
Player/ # GlobalGameData, player state
UI/ # All UI controllers and views
Quests/ # Quest system (objectives, rewards, tracking)
SDK/ # Public API interfaces
GameFlow/ # Game state management
Configuration/ # ScriptableObject configs
Rendering/ # URP shaders, time-of-day, weather
Audio/ # AudioSettingsManager, AudioConfiguration (the playback engine is in Foundation)
Localization/ # Multi-language support
Badges/ # Badge tracking
Input/ # Input abstraction
MonsterDex/ # Dex tracking (seen/caught)
Animation/ # Animation utilities
WorldGenerator/ # Procedural region generator
Licensing/ # Tier and license checks
Shaders/ # Shader source files
Editor/ # Editor-only tooling
MonsterCreator/ # Monster creation wizard
Tools/ # Database Browser, Type Chart Editor, validators
Followers/ # Follower spritesheet tools
Actors/ # CommandGraph editor
Hub/ # The OpenMon Hub window
MapEditor/ # Scene View map painting overlays
Callbacks/ # Editor callback utilities

依赖规则​

  1. Core Runtime 对 Online 或 Importer 模块没有任何外部依赖。
  2. 在线功能通过 MCE_ONLINE 脚本宏定义符号按需启用。
  3. 编辑器代码通过 .asmdef 边界与运行时严格分离。
  4. Runtime/SDK/ 中的 SDK 接口是面向已编译(DLL)版本用户的公共 API 范围。

程序集结构​

MCE 使用 Unity 的 Assembly Definition 来强制编译边界:

程序集位置用途引用
OpenMon.Foundation.RuntimeFoundation/Runtime/共享框架:DI、音频、本地化、场景管理、补间Zenject 和 Unity 包
OpenMon.Foundation.EditorFoundation/Editor/Foundation 的编辑器工具Foundation Runtime + UnityEditor
OpenMon.MCE.RuntimeCore/Runtime/核心游戏系统仅 Foundation 库
OpenMon.MCE.EditorCore/Editor/编辑器工具Runtime + UnityEditor
OpenMon.Hub.EditorCore/Editor/Hub/OpenMon Hub 窗口(它按名称调用导入器和在线工具,因此不依赖二者)Foundation Runtime
MCE.Online.RuntimeOnline/Runtime/Nakama 网络Runtime + Nakama SDK
MCE.Online.EditorOnline/Editor/在线编辑器工具Online Runtime + UnityEditor
MCE.EssentialsImporter.RuntimeImporter/Runtime/PBS/rxdata 解析器和导入验证器Runtime + KaitaiStruct
MCE.EssentialsImporter.EditorImporter/Editor/导入流水线Runtime + KaitaiStruct
OpenMon.SetupSetup/包依赖安装器和快速设置无(有意隔离)
MCE.Runtime.TestsCore/Tests/Runtime/Core 的播放模式测试Runtime + NUnit
MCE.Editor.TestsCore/Tests/Editor/Core 的编辑模式测试Runtime + Editor + NUnit

为什么 Assembly Definition 很重要​

  • 编译隔离:对编辑器代码的更改不会重新编译运行时。
  • 依赖强制:编译器会阻止运行时引用编辑器 API。
  • 构建剥离:编辑器程序集会从玩家构建中排除。
  • 测试隔离:测试程序集可以引用内部成员,而无需在生产环境中公开它们。

依赖注入(Zenject)​

MCE 使用 Zenject(Extenject)及其安装器模式进行依赖注入。

安装器模式​

每个子系统都有自己的安装器来注册绑定。大多数是 ScriptableObject 安装器;战斗启动器的就很典型:

[CreateAssetMenu(menuName = "MCE/Dependency Injection/Battle Launcher Installer",
fileName = "BattleLauncherInstaller")]
public class BattleLauncherInstaller : LazySingletonScriptableInstaller<BattleLauncher>
{
public override void InstallBindings()
{
Reference.Reset();

Container.QueueForInject(Reference);

Container.Bind<IBattleLauncher>().FromInstance(Reference).AsSingle().Lazy();
}
}

BattleLauncher 本身就是一个 ScriptableObject(一个资产),安装器资产指向它。依赖注入页面列出了所有安装器。

关键安装器​

安装器绑定
GameFlowInstaller核心游戏流程服务
PlayerCharacterInstallerPlayerCharacter、角色服务
BattleLauncherInstallerIBattleLauncher,战斗入口点
GridInstaller场景的网格(一个 MonoInstaller)
SceneInfoInstallerSceneInfo、场景元数据
WorldDatabaseInstallerWorldDatabase、全局地图数据
TileDataInstaller图块类型数据
GlobalGridManagerInstaller跨地图导航
SavegameInstallerSavegameManager、序列化
EvolutionManagerInstallerEvolutionManager

约定​

  • 绑定按约定为 .AsSingle().Lazy()(单个实例,首次使用时创建)。
  • [Inject] 用于 Construct() 方法,而不是构造函数。
  • 运行时 UI 实例化使用 DialogManager.Factory 模式。
  • 场景作用域的安装器(例如 GridInstaller,一个 MonoInstaller)处理每个场景的服务。
  • 项目作用域的安装器(ProjectContext 预制体上的 ScriptableObject 资产)处理全局服务。

注入点​

public class SomeManager : MonoBehaviour
{
// Field injection (for MonoBehaviours).
// Note: of the SDK interfaces, only IMonsterCaptureEngine is bound —
// take Database/Battle/Player/SaveSystem from its properties.
[Inject] private IMonsterCaptureEngine engine;

private IBattleSystem battle;

private void Awake()
{
battle = engine.Battle;
}

// Method injection (preferred for explicit dependencies on engine-internal types)
[Inject]
public void Construct(MCESettings settings, MonsterDatabaseInstance database)
{
this.settings = settings;
this.database = database;
}
}

关键运行时系统​

MCEInitialization 流程​

MCEInitialization 是游戏启动时最先运行的东西:

1. Screen setup (resolution, orientation)
2. Load databases (monsters, moves, items, abilities)
3. Download localization data (if remote)
4. Run first-time wizard (if needed)
5. Initialize subsystems (save manager, audio, input)
6. Signal ready
7. Load main menu or continue saved game

它是一个位于持久 GameObject 上、在场景加载之间保持存活的 MonoBehaviour。

怪物系统​

怪物的数据模型:

MonsterEntry (ScriptableObject - species definition)
├── Base stats, types, abilities
├── List<DataByFormEntry> (private per-form overrides)
├── DataByFormEntry.Evolutions: List<EvolutionData> (evolution paths)
└── Move lists (level-up, TM, egg, tutor)

MonsterInstance (runtime - individual monster)
├── Species reference (MonsterEntry)
├── Level, XP
├── IVs[6], EVs[6]
├── Nature
├── Current HP, Status
├── MoveSlot[4] (with PP)
├── Friendship, OT data
└── Form index

Roster (party of up to 6 MonsterInstances)
MonsterStorage (PC boxes)

战斗系统​

深入了解请参见战斗内部机制。

世界系统​

GlobalGridManager (cross-map navigation)
└── GridController (per-scene grid)
├── TileData grid (collision, type per tile)
├── EncounterTile zones
└── SceneInfo (metadata)

MCECharacterController (movement)
├── PlayerCharacter (input + encounters)
└── ActorCharacter (NPC AI movement)

存档系统​

SavegameManager
├── MCESavesSerializer (JSON read/write)
├── SavableObject[] (registered savables)
└── GameVariables (global state)

代码风格​

代码库遵循以下约定:

  • 4 空格缩进,大括号另起一行。
  • 公共类型、方法、属性使用 PascalCase。
  • 私有字段和局部变量使用 camelCase。
  • 用 [SerializeField] private 取代公共字段。
  • 通过 .asmdef 边界分离 Runtime/ 和 Editor/。
  • Conventional Commits:feat:、fix:、docs:、refactor:。
序列化字段

没有迁移计划时,绝不要重命名序列化字段。场景和预制体依赖序列化字段的名称。重命名请使用 [FormerlySerializedAs("oldName")]。

扩展引擎​

添加新系统​

  1. 在 Runtime/YourSystem/ 中创建你的类。
  2. 为依赖注入创建一个安装器。
  3. 把安装器添加到合适的场景上下文或项目上下文中。
  4. 如果它需要存档数据,请扩展 SavableObject。
  5. 如果它有编辑器工具,请在 Editor/YourSystem/ 中创建类。

修改现有系统​

  1. 阅读现有代码,理解该模块的依赖关系。
  2. 检查是否可以通过配置或扩展,而不是修改来完成这项更改。
  3. 如果要修改,请遵循该模块中现有的模式。
  4. 更新测试以覆盖这次更改。
  5. 在提交信息中记录这次更改。

性能注意事项​

  • 通过 SDK 的数据库查询(GetMonster(dexNumber)、GetMonster(name))会扫描物种列表。如果你经常调用,请缓存该条目。
  • 战斗模块是战斗 GameObject 的组件,随它每场战斗创建一次,而不是每回合一次。
  • 大型瓦片地图在每一步和每一帧的开销更大。把非常大的地图拆分为相连的场景(参见基于网格的移动中的 Neighbours)。
  • 存档序列化在主线程上是同步的。请保持存档数据的大小合理。
  • CommandGraph 的执行基于协程。包含大量节点的复杂图用于事件脚本没有问题,但不应用于逐帧逻辑。