Firstborn/Library/PackageCache/com.unity.burst@1.8.4/Documentation~/modding-support.md

242 lines
8.4 KiB
Markdown
Raw Normal View History

# Modding support
2023-03-28 13:24:16 -04:00
From Unity 2021.1, you can load additional Burst compiled libraries, which provide a way to create modifications that use Burst compiled code.
2023-03-28 13:24:16 -04:00
Burst only provides a method to load additional libraries, and doesn't provide any tooling to create mods. You need a copy of the Unity Editor to compile the additional libraries.
2023-03-28 13:24:16 -04:00
This section gives an example approach to modding with Burst and is a proof of concept.
2023-03-28 13:24:16 -04:00
## Supported uses
2023-03-28 13:24:16 -04:00
You can use this function in Play mode (or Standalone Players) only.
2023-03-28 13:24:16 -04:00
Make sure you load the libraries as soon as possible, and before the first Burst compiled use of a C# method. Unity unloads any Burst libraries that `BurstRuntime.LoadAdditionalLibraries` loads when you exit Play mode in the Editor, quit a Standalone Player.
2023-03-28 13:24:16 -04:00
## Example modding system
2023-03-28 13:24:16 -04:00
>[!NOTE]
>This example is limited in scope. You should have knowledge of assemblies and asmdefs to follow this example.
2023-03-28 13:24:16 -04:00
This example declares an interface that the mods abide by:
2023-03-28 13:24:16 -04:00
```c#
using UnityEngine;
public interface PluginModule
{
void Startup(GameObject gameObject);
void Update(GameObject gameObject);
}
```
You can use this interface to create new classes which follow these specifications and ship it separate to your application. Passing a single `GameObject` along limits the state that the plug-ins can affect.
2023-03-28 13:24:16 -04:00
### Modding manager
The following is an example of a modding manager:
2023-03-28 13:24:16 -04:00
```c#
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>();
// If mods are disabled, early out - this allows us to disable mods, enter Play Mode, exit Play Mode
//and be sure that the managed assemblies have been unloaded (assuming DomainReload occurs)
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 (assuming windows 64bit)
if (File.Exists(burstedAssembly))
{
BurstRuntime.LoadAdditionalLibrary(burstedAssembly);
}
}
}
foreach (var plugin in plugins)
{
plugin.Startup(objectForPlugins);
}
}
// Update is called once per frame
void Update()
{
foreach (var plugin in plugins)
{
plugin.Update(objectForPlugins);
}
}
}
```
This code scans the "Mods" folder, and for each folder it finds within, it attempts to load both a managed dll and a Burst compiled dll. It does this by adding them to an internal list that it can then iterate on and call the respective interface functions.
2023-03-28 13:24:16 -04:00
The names of the files are arbitrary: see [Simple Create Mod Menu Button](#simple-create-mod-menu-button), which is the code that generated those files.
2023-03-28 13:24:16 -04:00
Because this code loads the managed assemblies into the current domain, you need a domain reload to unload those before you can overwrite them. Unity automatically unloads the Burst dll files automatically unloaded when you exit Play mode. This is why a Boolean to disable the modding system is included, for testing in the Editor.
2023-03-28 13:24:16 -04:00
### A mod that uses Burst
Create a separate Unity project for this to use the project to produce the mod.
The following script attaches to a UI Canvas that contains a text component called **Main UI Label**, and changes the text when the mod is used. The text is either **Plugin Updated : Bursted** or **Plugin Updated : Not Bursted**. You will see the **Plugin Updated : Bursted** by default, but if you comment out the lines that load the Burst library in the PluginManager above, then the Burst compiled code doesn't load and the message changes appropriately.
2023-03-28 13:24:16 -04:00
```c#
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();
}
}
}
```
Put the above script in a folder along with an assembly definition file with an assembly name of `TestMod_Managed`, so that the next script can locate the managed part.
2023-03-28 13:24:16 -04:00
### Simple Create Mod Menu button
2023-03-28 13:24:16 -04:00
The below script adds a menu button. When you use the menu button, it builds a Standalone Player, then copies the C# managed dll and the `lib_burst_generated`.dll into a chosen Mod folder. This example assumes you are using Windows.
2023-03-28 13:24:16 -04:00
```c#
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");
// Get filename.
string path = EditorUtility.SaveFolderPanel("Choose Final Mod Location", "", "");
FileUtil.DeleteFileOrDirectory(buildFolder);
Directory.CreateDirectory(buildFolder);
// Build player.
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)
{
// Copy Managed library
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)) // Managed side not unloaded
FileUtil.CopyFileOrDirectory(managedSrc, managedDest);
else
Debug.LogWarning($"Couldn't update manged dll, {managedDest} is it currently in use?");
// Copy Burst library
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?");
}
}
}
```