This commit is contained in:
SoulliesOfficial
2026-04-17 12:01:50 -04:00
parent dd2657573a
commit ac98ec3aef
438 changed files with 4505 additions and 428 deletions

View File

@@ -0,0 +1,256 @@
# 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` 会发出警告。