11 KiB
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# 逻辑:
- 源码(.cs 文件):UMod 在导出时将 Modder 编写的 .cs 文件编译为临时程序集
uMod.Scripts,打包进 .umod 文件。加载时在运行时进行再次编译。 - 预编译 DLL:Modder 也可以提供预编译的
.dll文件,UMod 直接将其加载进 AppDomain。这是 Continentis 当前的实际工作方式(查看项目根目录下的umod-compiled-*.pdb文件)。
4.2 Host 如何访问 Mod 脚本中的类型
// 方式1:通过 ModManager(Continentis 项目的封装层)
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
// 检查资产是否存在
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 等列入黑名单。
八、ModTools(Modder 工具链)
8.1 游戏开发者需要提供给 Modder 的内容
通过 Tools → uMod 2.0 → Mod Tools Builder 在 Unity 编辑器中生成 Mod 制作专用工程,其中包含:
- Shared API DLL(或源码)—— Modder 引用此 DLL 来继承游戏基类
- UMod Exporter(编辑器工具)—— Modder 用来将自己的 Unity 项目打包为
.umod文件 - 示例 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 中)
十、关键限制与注意事项
- IL2CPP 不兼容:必须使用 Mono 脚本后端(Build Settings 中确认)
- ScriptableObject 只能在 Mod 项目中创建,不能在运行时通过代码
ScriptableObject.CreateInstance<T>()创建然后注册(因为 AssetBundle 需要序列化这些资产) - Unity 版本锁定:Modder 必须使用与主游戏完全相同的 Unity 版本,否则会出现
UnityVersionIncompatibility错误 - GameObject 必须实例化:从
host.Assets.Load<GameObject>()取到的是 Prefab 原始资产,必须通过Instantiate()才能放入场景使用 [SerializeField]的序列化限制:Mod 脚本中[SerializeField]标记的字段,其类型必须是主游戏已知的类型或 Unity 原生类型,否则无法正确序列化- 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 会发出警告。