关于 unity-modding
为 Unity 游戏开发者提供社区模组架构指南,涵盖资源加载、脚本 API、模组管理和现有游戏改造。内容从数据与资源模组延伸到 Lua 脚本及 Harmony 补丁,并包含安全建议。
主要功能
- 01数据、资源、脚本与代码模组分层
- 02Addressables 与 Asset Bundles 加载
- 03MoonSharp Lua 脚本 API
- 04模组清单、依赖与加载顺序
- 05Steam Workshop 与 Harmony 指引
- 06脚本沙箱安全建议
适用场景
- 01为新 Unity 游戏规划模组支持
- 02运行时加载模组资源与配置
- 03构建 Lua 模组接口和管理器
- 04为现有游戏设计代码补丁
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ josiahsiegel/ claude-plugin-marketplace --skill unity-modding安装后在 Claude Code 中输入/unity-modding确认已加载。
详细介绍
Unity 模组架构
概述
本指南介绍如何设计支持社区模组的 Unity 游戏,涵盖资源加载系统、脚本化模组 API、模组管理器模式、分发平台以及修改现有游戏的技巧。
模组架构层级
| 层级 | 模组作者可以做什么 | 复杂度 |
|---|---|---|
| 数据模组 | 替换纹理、声音、配置(JSON/XML) | 低 |
| 资源模组 | 通过 Asset Bundles 添加新模型、地图、物品 | 中 |
| 脚本模组 | 通过 Lua/脚本 API 添加自定义游戏逻辑 | 高 |
| 代码模组 | 通过 Harmony/BepInEx 修补游戏程序集 | 专家级 |
从一开始就将游戏设计为至少支持第 1 至 2 层。第 3 至 4 层需要精心设计 API。
模组资源加载
Addressables(推荐)
Addressables 提供统一的 API,可从本地 Asset Bundle、远程服务或模组目录加载资源。
// Load a mod's asset catalog at runtime
public async Awaitable LoadModCatalog(string modPath)
{
string catalogPath = Path.Combine(modPath, "catalog.json");
var locator = await Addressables.LoadContentCatalogAsync(catalogPath);
Debug.Log($"Loaded mod catalog with {locator.Keys.Count()} assets");
}
// Load an asset by address (works for both base game and mods)
var prefab = await Addressables.LoadAssetAsync<GameObject>("enemies/goblin");
Instantiate(prefab);
模组工作流:
- 模组作者使用配置了 Addressables 的 Unity 项目
- 将 Addressable 组构建为 Asset Bundles
- 打包并分发 catalog.json 和 .bundle 文件
- 游戏在运行时从模组目录加载 catalog
Asset Bundles(传统方式,但更简单)
public async Awaitable<AssetBundle> LoadModBundle(string path)
{
var request = AssetBundle.LoadFromFileAsync(path);
await request;
return request.assetBundle;
}
// Load specific asset from bundle
var bundle = await LoadModBundle("mods/weapons/swords.bundle");
var swordPrefab = bundle.LoadAsset<GameObject>("BroadSword");
关键规则:
- 不要重复加载同一个 Bundle(跟踪已加载的 Bundle)
- 禁用模组时卸载 Bundle:
bundle.Unload(true) - 本地文件请使用
AssetBundle.LoadFromFileAsync(不要使用LoadFromMemory)
支持热重载的数据模组
对于简单的数据模组(JSON 配置、CSV 表格):
public T LoadModConfig<T>(string modPath, string fileName)
{
string filePath = Path.Combine(modPath, fileName);
if (!File.Exists(filePath)) return default;
string json = File.ReadAllText(filePath);
return JsonUtility.FromJson<T>(json);
}
使用 FileSystemWatcher 检测更改,并在开发过程中热重载。
使用 MoonSharp 编写 Lua 脚本
MoonSharp 是一个用 C# 编写的 Lua 解释器,可在所有 Unity 平台上运行。
设置
通过 NuGet 安装或下载 DLL。为模组作者注册安全的 API:
public class ModScriptEngine
{
Script _lua;
public void Initialize()
{
_lua = new Script();
// Register safe game API
_lua.Globals["SpawnEntity"] = (Func<string, float, float, float, bool>)SpawnEntity;
_lua.Globals["GetPlayerHealth"] = (Func<float>)(() => player.Health);
_lua.Globals["ShowMessage"] = (Action<string>)ShowMessage;
_lua.Globals["RegisterEvent"] = (Action<string, DynValue>)RegisterEvent;
}
public void LoadMod(string luaFilePath)
{
string code = File.ReadAllText(luaFilePath);
_lua.DoString(code);
}
bool SpawnEntity(string id, float x, float y, float z)
{
// Validate and spawn - never trust mod input
if (!entityRegistry.Contains(id)) return false;
var pos = new Vector3(x, y, z);
if (!IsValidSpawnPosition(pos)) return false;
entityRegistry.Spawn(id, pos);
return true;
}
}
模组作者编写的 Lua 脚本
-- my_mod/init.lua
RegisterEvent("OnPlayerEnterZone", function(zoneName)
if zoneName == "boss_arena" then
SpawnEntity("dragon_boss", 0, 5, 0)
ShowMessage("A dragon appears!")
end
end)
安全注意事项
| 风险 | 缓解措施 |
|---|---|
| 文件系统访问 | 不要向 Lua 暴露 System.IO |
| 无限循环 | 设置指令数限制:script.Options.InstructionLimit = 100000 |
| 内存滥用 | 限制表的大小,监控内存分配 |
| 网络访问 | 切勿暴露 HTTP/socket API |
| 反射滥用 | MoonSharp 默认使用沙箱,但仍需验证已注册的类型 |
模组管理器模式
架构
Game
├── ModManager (singleton)
│ ├── DiscoverMods(modsFolder) // Scan for mod manifests
│ ├── ValidateMod(manifest) // Version compat, dependencies
│ ├── LoadMod(modId) // Load assets + scripts
│ ├── UnloadMod(modId) // Clean teardown
│ └── GetLoadOrder() // Dependency-sorted order
├── ModManifest (ScriptableObject / JSON)
│ ├── id, name, version, author
│ ├── dependencies[]
│ ├── gameVersionMin / gameVersionMax
│ └── entryPoint (script path)
└── ModSandbox
├── Lua scripting environment
└── Restricted API surface
模组清单格式
{
"id": "com.author.mymod",
"name": "My Awesome Mod",
"version": "1.2.0",
"author": "ModAuthor",
"description": "Adds new enemies and weapons",
"gameVersionMin": "1.0.0",
"gameVersionMax": "2.0.0",
"dependencies": ["[email protected]"],
"entryPoint": "scripts/init.lua",
"assets": "bundles/"
}
Steam Workshop 集成
使用 Steamworks.NET 或 Facepunch.Steamworks 集成 Steam Workshop:
- 上传: 模组作者打包模组文件夹,并调用
SteamUGC.CreateItem+SteamUGC.SubmitItemUpdate - 订阅: 玩家在 Steam Workshop 界面中订阅
- 下载: 游戏在启动时调用
SteamUGC.GetSubscribedItems()查询 - 加载: 从
SteamUGC.GetItemInstallInfo返回的路径读取,并交由 ModManager 加载
Harmony 修补(修改现有游戏)
模组作者会通过 BepInEx/MelonLoader 使用 Harmony,在运行时修补已编译的游戏代码。
[HarmonyPatch(typeof(PlayerHealth), "TakeDamage")]
class DamageModifier
{
// Prefix: runs before original method
static bool Prefix(ref float damage)
{
damage *= 0.5f; // Halve all damage
return true; // true = continue to original
}
// Postfix: runs after original method
static void Postfix(PlayerHealth __instance)
{
Debug.Log($"Health after damage: {__instance.CurrentHealth}");
}
}
如果要设计一款支持通过 Harmony 修改的游戏,请避免过度混淆代码,并在更新期间保持方法签名稳定。
更多资源
参考文件
references/mod-framework-detail.md—— 完整的模组管理器实现、依赖解析算法、模组加载顺序、版本化 API 模式、热重载系统、模组测试框架、BepInEx/MelonLoader 设置指南
评论
支持 Markdown