# UMod 2.0 系统详解 > **来源**:uMod 2.0 UserGuide.pdf + UMod Plugin XML API 文档 + ExampleScripts > **整理日期**:2026-03-13 > **适用角色**:游戏设计师、技术员、Mod 制作者 > **本文档用途**:供所有团队成员快速掌握 UMod 的核心机制、API 风格与 Continentis 项目中的具体应用模式 --- ## 一、UMod 是什么? UMod 2.0 是一款 Unity 插件,让游戏开发者能够**在发布后的游戏中支持社区 Mod**。其核心目标是: - 允许 Modder 在独立的 Unity 项目中创建内容(资产、脚本、场景) - 将内容打包为单个 `.umod` 文件 - 游戏在运行时动态加载该文件并使用其中的内容 **重要限制**:UMod **不支持 IL2CPP**(因为 IL2CPP 禁止运行时动态加载程序集)。因此使用 UMod 的游戏必须使用 **Mono 后端**。 --- ## 二、核心概念与术语 | 术语 | 说明 | |---|---| | **ModHost** | 管理单个 Mod 生命周期的核心对象。负责加载、卸载、资产访问、脚本访问 | | **Mod.Load()** | 静态方法,传入 Mod 路径 URI,返回一个 `ModHost` 实例 | | **host.Assets** | 访问 Mod 中所有打包资产的接口(类似 `Resources.Load`)| | **host.IsModLoaded** | 布尔值,判断 Mod 是否已成功加载 | | **host.LoadResult** | 包含 `ModLoadError` 枚举,详细描述加载失败原因 | | **ModDirectory** | 指定游戏扫描 Mod 的目录路径(如 `persistentDataPath + "/Mods"`)| | **ModSearch** | 扫描目录以发现有效 `.umod` 文件的组件 | | **ModManifest** | Mod 内部的元数据资产(名称、版本、作者等),可在加载前读取 | | **ModInfoResource** | 在加载 Mod 前即可读取的外部信息(通过 `ModSearch` 获取)| | **ModObjectIdentity** | 自动附加到 Mod 内所有对象上的组件,供 Host 追踪和管理 Mod 对象生命周期 | | **LinkBehaviour** | 存储 Mod 脚本的类型信息,使 Host 可以在运行时重新链接正确的 C# 类 | --- ## 三、Mod 加载流程(完整步骤) ``` 1. [搜索] ModSearch 扫描目录,发现 .umod 文件 ↓ 2. [加载前预读] 通过 ModInfoResource 读取 Mod 元数据(名称、版本、依赖) ↓ 3. [加载触发] ModHost host = Mod.Load(new Uri(modPath)); ↓ 4. [UMod 内部处理] a. 读取并挂载 AssetBundle b. 加载 Mod 内的程序集(.dll 或编译后的 .cs)进入 AppDomain c. 通过 LinkBehaviour 重新链接所有脚本组件到对应 C# 类型 d. 触发 ModObjectIdentity.OnModObjectCreated 事件 ↓ 5. [就绪状态] host.IsModLoaded == true ↓ 6. [资产访问] host.Assets.Load("资产名称") ↓ 7. [类型访问] host.Assets.Load("数据名称") ↓ 8. [卸载] host.UnloadMod() → 自动销毁所有已追踪的 Mod 对象 ``` ### 常见加载错误码(ModLoadError) | 错误码 | 原因 | |---|---| | `NoError` | 成功 | | `InvalidMod` | 文件损坏或非 UMod 格式 | | `InvalidPath` | 路径无效 | | `ModNotFound` | 路径有效但文件不存在 | | `MissingReferences` | Mod 依赖的其他 Mod 未加载 | | `SecurityError` | Mod 脚本违反了 Host 设置的安全白名单 | | `UnityVersionIncompatibility` | Unity 版本不匹配 | --- ## 四、C# 脚本在 Mod 中的工作方式 ### 4.1 脚本打包方式 Mod 可以通过两种方式包含 C# 逻辑: 1. **源码(.cs 文件)**:UMod 在导出时将 Modder 编写的 .cs 文件编译为临时程序集 `uMod.Scripts`,打包进 .umod 文件。加载时在运行时进行再次编译。 2. **预编译 DLL**:Modder 也可以提供预编译的 `.dll` 文件,UMod 直接将其加载进 AppDomain。这是 Continentis 当前的实际工作方式(查看项目根目录下的 `umod-compiled-*.pdb` 文件)。 ### 4.2 Host 如何访问 Mod 脚本中的类型 ```csharp // 方式1:通过 ModManager(Continentis 项目的封装层) Type logicType = ModManager.GetType(typeID); object instance = Activator.CreateInstance(logicType); // 方式2:通过 UMod 原生 API(获取所有实现了某接口的类型实例) IEnumerable instances = host.GetInstances(); // 方式3:通过 ScriptDomain(直接访问脚本程序集中的类型) Type[] allTypes = host.ScriptDomain.GetTypes(); ``` ### 4.3 在 Continentis 中的具体应用 Continentis 的 `ModManager` 类封装了 UMod 的类型访问,格式为: ``` TypeID = "{ModName}/{Category}/{SubCategory}/{ClassName}" 例如:"Basic/Cards/Assassin/Backstab" ``` 这使得 **CardData**(纯数据)与 **CardLogicBase**(行为代码)可以独立来自不同 Mod,实现了真正的数据-逻辑解耦。 --- ## 五、资产的打包与访问 ### 5.1 打包规则 - Mod 内所有资产通过 Unity 的 **AssetBundle** 系统打包 - 打包时 UMod Exporter 会自动为每个 GameObject 附加 `ModObjectIdentity` 组件 - 资产名称即为 Unity 编辑器中的资产文件名(不含扩展名) ### 5.2 资产访问 API ```csharp // 检查资产是否存在 bool exists = host.Assets.Exists("资产名"); // 加载资产(同步,类似 Resources.Load) GameObject prefab = host.Assets.Load("prefab名") as GameObject; ScriptableObject data = host.Assets.Load("数据名"); // 实例化(必须先实例化才能使用 GameObject,否则会影响编辑器) GameObject instance = Instantiate(prefab, Vector3.zero, Quaternion.identity); // 在 Continentis 的 ModManifest.SaveToDatabase 中的实际用法: T data = host.Assets.Load(assetName); ModManager.Database[typeof(T)].TryAdd(assetName, data); ``` ### 5.3 支持的资产类型 - Prefabs、Materials、Textures、Audio、Sprites - **ScriptableObject**(必须在 Mod 项目中创建,不能在运行时用代码创建) - Scenes(场景也可以打包) - AnimationClips、Fonts 等 Unity 原生资产类型 --- ## 六、Shared API(共享接口层)概念 **这是 Mod 系统设计中最关键的架构概念。** ### 6.1 什么是 Shared API Shared API 是 Game → Modder 的**契约层**。游戏开发者将所有"Mod 可以继承或实现的基类"打包成一个单独的 DLL(或源码),Modder 在自己的 Unity 项目中**引用**这个 DLL,即可编写符合主游戏期望的 Mod 脚本。 ### 6.2 在 Continentis 中的对应关系 | Shared API 层 | Continentis 实现 | |---|---| | Mod 可继承的基类 | `CardLogicBase`、`CharacterLogicBase`、`CharacterCombatBuffBase`、`IntentionBase` 等 | | Mod 可使用的工具类 | `CardAssistanceFunctions`、`CharacterAssistanceFunctions` 等 | | 数据定义类 | `CardData`、`CharacterData`、`EquipmentData`(ScriptableObject)| | API 访问层 | `ModManager`、`CombatMainManager`、`CombatUIManager` 等 Singleton | | 命令系统 | `CommandBase`、`CommandGroup`、`Cmd_*` 系列命令类 | 这些类合在一起,就是 Continentis 事实上的"Shared API",但它们目前**并没有被正式打包为独立 DLL** 分发给 Modder。 --- ## 七、安全等级(Security Level) UMod 提供三种脚本安全策略(在 Host 端配置): | 策略 | 说明 | Continentis 建议 | |---|---|---| | **Allow All** | 不做任何检查,Mod 脚本可以访问任意 C# API | 仅开发阶段使用 | | **Reject Only Specified** | 黑名单模式,明确禁止某些危险 API(如 `System.IO`、`System.Net`)| 对外发布时的妥协方案 | | **Allow Only Specified** | 白名单模式,仅允许明确指定的命名空间/类型 | 最安全,但配置成本高 | **Continentis 注意**:由于开发者明确允许不加密并可暴露源码,建议对内部测试 Mod 使用 **Allow All**,若未来开放社区则切换为 **Reject Only Specified**,将 `System.IO.File`、`System.Net.*`、`System.Reflection.Emit` 等列入黑名单。 --- ## 八、ModTools(Modder 工具链) ### 8.1 游戏开发者需要提供给 Modder 的内容 通过 `Tools → uMod 2.0 → Mod Tools Builder` 在 Unity 编辑器中生成 **Mod 制作专用工程**,其中包含: 1. **Shared API DLL**(或源码)—— Modder 引用此 DLL 来继承游戏基类 2. **UMod Exporter**(编辑器工具)—— Modder 用来将自己的 Unity 项目打包为 `.umod` 文件 3. **示例 Mod 项目**(可选)—— 供 Modder 参考的模板 ### 8.2 Modder 的标准工作流 ``` 1. 新建 Unity 项目(必须与主游戏使用同一版本的 Unity) 2. 导入 Shared API DLL 3. 导入 UMod Exporter 工具 4. 创建 CardData.asset(ScriptableObject)- 填写卡牌数值 5. 编写 MyCard.cs(继承 CardLogicBase) 6. (可选)创建卡牌图片、动画、音效等资产 7. 通过 UMod Exporter 一键打包为 MyMod.umod 8. 将 .umod 文件放入游戏的 Mods 目录 ``` --- ## 九、Mod 包的内部结构 一个 `.umod` 文件(本质是 AssetBundle 自定义格式的归档)包含: ``` MyMod.umod ├── [ModInfo] ← 元数据:名称/版本/作者/依赖/Unity版本等 ├── [AssemblyInfo] ← 枚举本 Mod 包含的所有程序集 ├── Assembly-CSharp.dll(或编译后的 .cs) │ └── 包含所有 Modder 编写的 C# 类 ├── ScriptableObject 资产(CardData.asset 等) ├── Prefab 资产(角色模型、特效等) ├── Sprite/Texture 资产(卡牌图片等) └── [ModObjectIdentity] 信息(存储于各 Asset 中) ``` --- ## 十、关键限制与注意事项 1. **IL2CPP 不兼容**:必须使用 Mono 脚本后端(Build Settings 中确认) 2. **ScriptableObject 只能在 Mod 项目中创建**,不能在运行时通过代码 `ScriptableObject.CreateInstance()` 创建然后注册(因为 AssetBundle 需要序列化这些资产) 3. **Unity 版本锁定**:Modder 必须使用与主游戏完全相同的 Unity 版本,否则会出现 `UnityVersionIncompatibility` 错误 4. **GameObject 必须实例化**:从 `host.Assets.Load()` 取到的是 Prefab 原始资产,必须通过 `Instantiate()` 才能放入场景使用 5. **`[SerializeField]` 的序列化限制**:Mod 脚本中 `[SerializeField]` 标记的字段,其类型必须是主游戏已知的类型或 Unity 原生类型,否则无法正确序列化 6. **Mod 间依赖**:一个 Mod 可以声明依赖另一个 Mod(通过 ModManifest 的依赖列表),加载时 UMod 会检查依赖是否已加载 --- ## 十一、在 Continentis 中的应用现状 ### 当前 Basic Mod 的工作方式(编辑器内模式) Basic Mod 目前**不是外部 .umod 文件**,而是直接作为 `Assembly-CSharp` 的一部分运行在编辑器内。这是 UMod 支持的"In-Editor Mod"模式,用于开发阶段便捷测试。 实际外部 Mod 文件存放于 `Assets/ExportedMods/` 目录,并对应在根目录有大量的 `umod-compiled-*.pdb` 符号文件,说明已有若干编译产物。 ### Mod 资产命名规范(当前项目约定) ``` {AssetType}_{ModName}_{AssetName} 示例:CardData_Basic_Backstab CharacterData_Basic_Assassin EquipmentData_Basic_SteelBracer ``` 命名必须符合 `^\w+_\w+_.+$` 正则,否则 `ModManifest` 会发出警告。