Files
Continentis/.agents/skills/game-designer-generic/knowledge/UMod_2.0_系统详解.md
SoulliesOfficial ac98ec3aef 更新
2026-04-17 12:01:50 -04:00

257 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<T>("资产名称")
7. [类型访问] host.Assets.Load<MyScriptableObject>("数据名称")
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通过 ModManagerContinentis 项目的封装层)
Type logicType = ModManager.GetType(typeID);
object instance = Activator.CreateInstance(logicType);
// 方式2通过 UMod 原生 API获取所有实现了某接口的类型实例
IEnumerable<IMyInterface> instances = host.GetInstances<IMyInterface>();
// 方式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<MyData>("数据名");
// 实例化(必须先实例化才能使用 GameObject否则会影响编辑器
GameObject instance = Instantiate(prefab, Vector3.zero, Quaternion.identity);
// 在 Continentis 的 ModManifest.SaveToDatabase 中的实际用法:
T data = host.Assets.Load<T>(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` 等列入黑名单。
---
## 八、ModToolsModder 工具链)
### 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.assetScriptableObject- 填写卡牌数值
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<T>()` 创建然后注册(因为 AssetBundle 需要序列化这些资产)
3. **Unity 版本锁定**Modder 必须使用与主游戏完全相同的 Unity 版本,否则会出现 `UnityVersionIncompatibility` 错误
4. **GameObject 必须实例化**:从 `host.Assets.Load<GameObject>()` 取到的是 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` 会发出警告。