MOD 作成のサポート
Unity 2021.1 以降では、追加の Burst コンパイル済みライブラリをロードできるため、そのライブラリを使用すると、Burst でコンパイルしたコードを使用する MOD を作成できます。
Burst で提供されるのは追加のライブラリをロードする手段のみで、MOD を作成するツールは用意されていません。追加のライブラリをコンパイルするには、Unity エディターが必要です。
このセクションでは、Burst での MOD 作成手法の例を示し、概念実証を行います。
サポート対象の用途
この関数は再生モード (またはスタンドアロンプレイヤー) でのみ使用できます。
ライブラリはできるだけ早く、Burst でコンパイルした C# メソッドを初めて使用する前にロードするようにしてください。BurstRuntime.LoadAdditionalLibraries
がロードした Burst ライブラリは、エディターで再生モードを終了したときや、スタンドアロンプレイヤーを終了したときにアンロードされます。
MOD 作成システムの例
Note
この例は対象範囲が制限されています。この例を実行するにはアセンブリと asmdef に関する知識が必要です。
以下の例では、MOD が準拠するインターフェースを宣言します。
using UnityEngine;
public interface PluginModule
{
void Startup(GameObject gameObject);
void Update(GameObject gameObject);
}
このインターフェースを使用すれば、これらの仕様を満たす新しいクラスを作成し、アプリケーションとは別にリリースできます。1 つの GameObject
を渡すことで、プラグインの影響を受ける状態が限定されます。
MOD 作成マネージャー
以下は、MOD 作成マネージャーの例です。
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using Unity.Burst;
public class PluginManager : MonoBehaviour
{
public bool modsEnabled;
public GameObject objectForPlugins;
List<PluginModule> plugins;
void Start()
{
plugins = new List<PluginModule>();
// MOD が無効になっている場合はすぐに抜けます。そうすることで MOD の無効化、再生モードの開始と終了、
//マネージドアセンブリがアンロードされたことの確認 (DomainReload が実行されることを想定) を行えます
if (!modsEnabled)
return;
var folder = Path.GetFullPath(Path.Combine(Application.dataPath, "..", "Mods"));
if (Directory.Exists(folder))
{
var mods = Directory.GetDirectories(folder);
foreach (var mod in mods)
{
var modName = Path.GetFileName(mod);
var monoAssembly = Path.Combine(mod, $"{modName}_managed.dll");
if (File.Exists(monoAssembly))
{
var managedPlugin = Assembly.LoadFile(monoAssembly);
var pluginModule = managedPlugin.GetType("MyPluginModule");
var plugin = Activator.CreateInstance(pluginModule) as PluginModule;
plugins.Add(plugin);
}
var burstedAssembly = Path.Combine(mod, $"{modName}_win_x86_64.dll"); // Burst dll (Windows 64 ビット版を想定)
if (File.Exists(burstedAssembly))
{
BurstRuntime.LoadAdditionalLibrary(burstedAssembly);
}
}
}
foreach (var plugin in plugins)
{
plugin.Startup(objectForPlugins);
}
}
// Update が 1 フレームに 1 回呼び出されます
void Update()
{
foreach (var plugin in plugins)
{
plugin.Update(objectForPlugins);
}
}
}
このコードでは "Mods" フォルダーをスキャンし、その中に見つかる各フォルダーで、マネージド dll と Burst でコンパイルされた dll の両方のロードを試みます。具体的には、これらの dll を内部リストに追加すると、そのリストで各インターフェース関数の呼び出しを繰り返すことができます。
ファイルの名前は任意です。それらのファイルを生成したコードである、シンプルな MOD 作成メニューボタン を参照してください。
このコードは、マネージドアセンブリを現在のドメインにロードするので、上書きする前にアンロードするために、ドメインの再ロードが必要です。再生モードを終了すると、Burst dll ファイルが自動的にアンロードされます。そのため、エディターでテストできるように MOD 作成システムを無効にするための Boolean が含まれています。
Burst を使用する Mod
このための個別の Unity プロジェクトを作成し、それを使用して MOD を生成します。
以下のスクリプトは、Main UI Label というテキストコンポーネントを含む UI キャンバスにアタッチされ、MOD が使用されたときにテキストを変更します。テキストは Plugin Updated : Bursted または Plugin Updated : Not Bursted のいずれかです。デフォルトでは Plugin Updated : Bursted が表示されますが、上記の PluginManager で Burst ライブラリをロードする行をコメントアウトすると、Burst でコンパイルされたコードはロードされず、メッセージが適切に変更されます。
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.UI;
public class MyPluginModule : PluginModule
{
Text textComponent;
public void Startup(GameObject gameObject)
{
var childTextComponents = gameObject.GetComponentsInChildren<Text>();
textComponent = null;
foreach (var child in childTextComponents)
{
if (child.name == "Main UI Label")
{
textComponent = child;
}
}
if (textComponent==null)
{
Debug.LogError("something went wrong and i couldn't find the UI component i wanted to modify");
}
}
public void Update(GameObject gameObject)
{
if (textComponent != null)
{
var t = new CheckBurstedJob { flag = new NativeArray<int>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory) };
t.Run();
if (t.flag[0] == 0)
textComponent.text = "Plugin Updated : Not Bursted";
else
textComponent.text = "Plugin Updated : Bursted";
t.flag.Dispose();
}
}
[BurstCompile]
struct CheckBurstedJob : IJob
{
public NativeArray<int> flag;
[BurstDiscard]
void CheckBurst()
{
flag[0] = 0;
}
public void Execute()
{
flag[0] = 1;
CheckBurst();
}
}
}
上記のスクリプトを、TestMod_Managed
というアセンブリ名のアセンブリ定義ファイルとともにフォルダー内に配置すると、次のスクリプトでマネージド部分を特定できます。
シンプルな MOD 作成メニューボタン
以下のスクリプトでは、メニューボタンを追加します。このメニューボタンを使用すると、スタンドアロンプレイヤーがビルドされ、選択した MOD フォルダーに C# マネージド dll と lib_burst_generated
.dll がコピーされます。この例は、Windows を使用していることを前提としています。
using UnityEditor;
using System.IO;
using UnityEngine;
public class ScriptBatch
{
[MenuItem("Modding/Build X64 Mod (Example)")]
public static void BuildGame()
{
string modName = "TestMod";
string projectFolder = Path.Combine(Application.dataPath, "..");
string buildFolder = Path.Combine(projectFolder, "PluginTemp");
// ファイル名を取得します。
string path = EditorUtility.SaveFolderPanel("Choose Final Mod Location", "", "");
FileUtil.DeleteFileOrDirectory(buildFolder);
Directory.CreateDirectory(buildFolder);
// プレイヤーをビルドします。
var report = BuildPipeline.BuildPlayer(new[] { "Assets/Scenes/SampleScene.unity" }, Path.Combine(buildFolder, $"{modName}.exe"), BuildTarget.StandaloneWindows64, BuildOptions.Development);
if (report.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
{
// マネージドライブラリをコピーします。
var managedDest = Path.Combine(path, $"{modName}_Managed.dll");
var managedSrc = Path.Combine(buildFolder, $"{modName}_Data/Managed/{modName}_Managed.dll");
FileUtil.DeleteFileOrDirectory(managedDest);
if (!File.Exists(managedDest)) // マネージド側がアンロードされない
FileUtil.CopyFileOrDirectory(managedSrc, managedDest);
else
Debug.LogWarning($"Couldn't update manged dll, {managedDest} is it currently in use?");
// Burst ライブラリをコピーします。
var burstedDest = Path.Combine(path, $"{modName}_win_x86_64.dll");
var burstedSrc = Path.Combine(buildFolder, $"{modName}_Data/Plugins/x86_64/lib_burst_generated.dll");
FileUtil.DeleteFileOrDirectory(burstedDest);
if (!File.Exists(burstedDest))
FileUtil.CopyFileOrDirectory(burstedSrc, burstedDest);
else
Debug.LogWarning($"Couldn't update bursted dll, {burstedDest} is it currently in use?");
}
}
}