unity-modding について
コミュニティによる MOD 対応や既存ゲームの改造を設計する Unity 開発者向けのアーキテクチャガイドです。アセット読み込み、スクリプト API、MOD 管理、配布方法と安全面を扱います。
主な機能
- 01データ、アセット、スクリプト、コード MOD の段階
- 02Addressables と Asset Bundles による読み込み
- 03MoonSharp の Lua スクリプト API
- 04マニフェスト、依存関係、読み込み順
- 05Steam Workshop と Harmony の解説
- 06スクリプトのサンドボックスに関する注意
活用シーン
- 01新しい Unity ゲームの MOD 対応設計
- 02実行時の MOD アセットと設定の読み込み
- 03Lua MOD API と管理機能の構築
- 04既存ゲーム向けコードパッチの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ josiahsiegel/ claude-plugin-marketplace --skill unity-moddingインストール後、Claude Code で/unity-moddingを入力して読み込まれたか確認します。
詳細
Unity の Mod 対応アーキテクチャ
概要
コミュニティによる Mod 制作に対応した Unity ゲームを設計するためのガイドです。アセット読み込みシステム、スクリプト Mod API、Mod マネージャーのパターン、配布プラットフォーム、既存ゲームを Mod 対応にする手法を扱います。
Mod 対応アーキテクチャの段階
| 段階 | Mod 制作者ができること | 複雑さ |
|---|---|---|
| データ Mod | テクスチャ、サウンド、設定ファイル(JSON/XML)の置き換え | 低 |
| アセット Mod | Asset Bundle を使った新しいモデル、マップ、アイテムの追加 | 中 |
| スクリプト Mod | Lua/スクリプト API を使ったカスタムゲームロジック | 高 |
| コード Mod | Harmony/BepInEx を使ったゲームアセンブリのパッチ適用 | エキスパート |
最初から少なくとも段階 1~2 に対応するよう、ゲームを設計してください。段階 3~4 には、意図的な API 設計が必要です。
Mod 用のアセット読み込み
Addressables(推奨)
Addressables は、ローカルバンドル、リモートサーバー、Mod ディレクトリからアセットを読み込むための統一 API を提供します。
// 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);
Mod 制作のワークフロー:
- Mod 制作者は Addressables を設定した Unity プロジェクトを使用します
- Addressable グループを Asset Bundle にビルドします
- catalog.json と .bundle ファイルを配布します
- ゲームは実行時に Mod ディレクトリからカタログを読み込みます
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");
主なルール:
- 同じバンドルを二度読み込まないでください(読み込み済みのバンドルを追跡します)
- Mod が無効化されたらバンドルをアンロードします:
bundle.Unload(true) - ローカルファイルには
AssetBundle.LoadFromFileAsyncを使用します(LoadFromMemoryは使用しません)
ホットリロード可能なデータ Mod
シンプルなデータ Mod(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 は、すべての Unity プラットフォームで動作する C# 製の Lua インタープリターです。
セットアップ
NuGet からインストールするか、DLL をダウンロードします。Mod 制作者向けに安全な 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;
}
}
Mod 制作者側の 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/ソケット API は公開しないでください |
| リフレクションの不正使用 | MoonSharp はデフォルトでサンドボックス化されていますが、登録する型を検証してください |
Mod マネージャーのパターン
アーキテクチャ
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
Mod マニフェストの形式
{
"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 との統合
Steam Workshop との統合には、Steamworks.NET または Facepunch.Steamworks を使用します:
- アップロード: Mod 制作者が Mod フォルダーをパッケージ化し、
SteamUGC.CreateItem+SteamUGC.SubmitItemUpdateを呼び出します - サブスクライブ: プレイヤーは Steam Workshop の UI からサブスクライブします
- ダウンロード: ゲームは起動時に
SteamUGC.GetSubscribedItems()を照会します - 読み込み:
SteamUGC.GetItemInstallInfoのパスから ModManager に読み込みます
Harmony によるパッチ適用(既存ゲームの Mod 対応)
Mod 制作者は、実行時にコンパイル済みゲームコードへパッチを適用するため、Harmony(BepInEx/MelonLoader 経由)を使用します。
[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 による Mod 対応を想定してゲームを設計する場合、過度なコード難読化を避け、アップデート後もメソッドシグネチャを安定させてください。
追加リソース
参考ファイル
references/mod-framework-detail.md-- Mod マネージャーの完全な実装、依存関係の解決アルゴリズム、Mod の読み込み順序、バージョン管理された API のパターン、ホットリロードシステム、Mod テスト用フレームワーク、BepInEx/MelonLoader のセットアップガイド