mcpField
U
unity-modding

unity-modding

github.com/josiahsiegel开发59126 次安装19 周前更新
代码仓库

关于 unity-modding

为 Unity 游戏开发者提供社区模组架构指南,涵盖资源加载、脚本 API、模组管理和现有游戏改造。内容从数据与资源模组延伸到 Lua 脚本及 Harmony 补丁,并包含安全建议。

主要功能

  1. 01数据、资源、脚本与代码模组分层
  2. 02Addressables 与 Asset Bundles 加载
  3. 03MoonSharp Lua 脚本 API
  4. 04模组清单、依赖与加载顺序
  5. 05Steam Workshop 与 Harmony 指引
  6. 06脚本沙箱安全建议

适用场景

  1. 01为新 Unity 游戏规划模组支持
  2. 02运行时加载模组资源与配置
  3. 03构建 Lua 模组接口和管理器
  4. 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);

模组工作流:

  1. 模组作者使用配置了 Addressables 的 Unity 项目
  2. 将 Addressable 组构建为 Asset Bundles
  3. 打包并分发 catalog.json 和 .bundle 文件
  4. 游戏在运行时从模组目录加载 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:

  1. 上传: 模组作者打包模组文件夹,并调用 SteamUGC.CreateItem + SteamUGC.SubmitItemUpdate
  2. 订阅: 玩家在 Steam Workshop 界面中订阅
  3. 下载: 游戏在启动时调用 SteamUGC.GetSubscribedItems() 查询
  4. 加载: 从 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