目录
自定义工具窗口
Mod工具包生成器允许您更改许多设置,以便将包定制为适合您的游戏,但是您仍将拥有默认的uMod导出器窗口以供选择涉及设置菜单。这些随附的窗口旨在成为通用窗口,并支持所有mod工具构建器可以生成的配置,但是您可能需要创建自己的配置编辑器窗口集,以便您可以进一步自定义您的mod工具。
为了创建uMod可以使用的编辑器窗口,您只需要创建一个编辑器窗口,就像您通常使用Unity API一样。该窗口可以实现为编辑器脚本或作为托管编辑器程序集的一部分,但它必须存在于编辑器文件夹中。为了有关使用Unity API创建编辑器窗口的信息,请看一下Unity编辑器文档。
我们还将uMod导出程序源代码包含在uMod项目中以便您可以对其进行修改以适合您的需求。创建编辑器窗口后,您需要将其与工具包链接。至为此,您需要将窗口变成uMod工具窗口,可以通过以下方式实现只需将“ [UModToolsWindow]”属性添加到编辑器窗口类声明中即可。你可以然后打开国防部工具构建器向导,并导航至“构建引擎->菜单”标签,现在应该可以在菜单项“显示窗口”操作之一中选择您的编辑器窗口。
using UMod;
using UnityEditor;
[UModToolsWindow]
public class ExampleModToolsWindow:EditorWindow
{
public void OnGUI(){
GUILayout.Label("Hello World");
}
}
Window Considerations 窗体注意事项
如果决定将编辑器窗口实现为C#脚本而不是程序集,则需要在mod工具包中手动包括必要的脚本。您可以通过Mod工具构建器的“内容”页面来执行此操作,在此页面中可以指定要包含的许多C#脚本。如果使用编辑器程序集包含编辑器窗口,则如果在向导窗口中引用了该程序集的一种类型,则该程序集将自动包含在内。
您可以包括任意数量的编辑器窗口和工具来帮助您的修改者创建游戏内容。它不必限于默认的导出器窗口。为了例如:对于赛车游戏,可能需要包括许多轨道创建编辑器帮助您的改装者设置赛道的工具。
您可以从自定义编辑器窗口与uMod构建引擎进行交互,以便您可以在不使用默认导出器窗口的情况下创建mod。您将主要想使用“ UMod.BuildEngine.ModToolsUtil”类可访问与默认导出程序相同的操作。
深入理解Mod
Load Mod 加载umod文件
在uMod 2.0中加载mod相对简单,可以使用一种方法来实现调用,但是我们确实需要知道mod的位置,然后才能加载它。 uMod能够从本地和远程位置加载mod,并将在以下过程中确定路径的位置加载中。
注意:如果mod位于远程服务器上,则由于mod的加载时间可能会大大增加
必须首先下载。 实际时间将取决于一些因素,例如mod的大小和
下载速度快,但可以在加载时检索下载统计信息和进度
eg:
using System;
using UnityEngine;
using UMod;
public class ModLoadExample:MonoBehaviour
{
private void Start()
{
Uri path = new Uri("file:///C:/Mods/TestMod.umod");
Mod.Load(path);
}
}
注意:指定mod路径时要注意的重要一点是,与以前的uMod版本不同,现在需要指定包含扩展名的mod文件的路径。
如您所见,发出mod加载请求非常容易,但是我们实际上如何知道 mod加载成功了吗?优良作法是实现一些错误检查代码,而不要 假设该mod将始终正确加载且没有错误。 uMod的目标是既简单又 使用起来直观,因此您可以通过访问单个属性来检查加载是否失败。的 当然,如果需要帮助,您还可以获得有关加载错误的更多信息 调试或错误报告。 首先,您可能已经注意到load方法的返回值是一个mod主机, 本质上是一个管理器对象,负责管理已加载的mod。有关详细 有关信息,请参阅“ Mod Host”部分。主机是一个状态对象,具有多个 代表主机当前状态的变量,例如:“ IsLoading”和“ IsLoaded”。这些 可以访问值来确定主机所处的状态,例如:“ IsLoaded”可用于 确定mod主机是否已加载有效的mod。
eg:
using System;
using UnityEngine;
using UMod;
public class ModLoadExample:MonoBehaviour{
private void Start()
{
Uri path = new Uri("file:///C:/Mods/TestMod.umod");
ModHost host = Mod.Load(path);
if(host.IsLoaded)
{
Debug.Log("The mod is loaded")
}
else{
Debug.Log("The mod could not be loaded - " + host.LoadResult.Message)
}
}
}
您现在可以发出mod加载请求,但是您可能会注意到调用加载该方法可能需要花费不确定的时间才能完成,而且不是最佳实践响应性应用程序,例如游戏。 通常,将加载屏幕显示为适当的进度值,可以告知用户正在发生的事情以及他们可以期望持续多长时间等待。 请看下一节,该节显示了如何实现异步mod加载这发生在后台线程上。
Async Mod Loading
为了使uMod易于在Unity中使用,我们尝试紧密模拟异步加载Unity使用的方法。 结果,uMod包含了许多自定义的可屈服对象,这些对象是由所有异步加载方法返回,这些方法包含诸如当前加载进度等信息以及最终结果。 如您所料,所有对象都是可屈服的并且可以使用协程。以下C#代码显示了一个mod异步加载的基本示例。 请注意如果现在为’ModAsyncOperation’,则返回加载方法的值。
eg:
using System;
using UnityEngine;
using UMod;
public class ModCoroutineExample : MonoBehaviour
{
private IEnumerator Start()
{
// Create a mod path
Uri path = new Uri("file:///C:/Mods/ExampleMod.umod");
// Create a request
ModAsyncOperation<ModHost> request = Mod.LoadAsync(path);
// Wait for the task to complete
yield return request;
// Check the status of the load
Debug.Log("Loaded = " + request.IsSuccessful);
}
注意:大多数情况下,实际加载将在后台线程上完成,因此您可以运行处理请求时执行的其他任务。 但是请注意,激活部分很小必须在主线程上执行的最大负载的百分比,因此您可能会看到轻微的负载峰值。
这一切都非常容易,并将所有繁重的工作从主线程转移开了,但是我们没有
知道负载已取得多少进展的一种方式。 知道我们是否会非常有用
希望为用户提供一个不错的加载栏或进度指示器。 幸运的是,我们可以访问
通过稍微修改上面的代码,异步加载请求的当前加载进度:
从网络上加载Mod
using System;
using UnityEngine;
using UMod;
public class ModProgressExample : MonoBehaviour
{
private ModHost host = null;
private void Start()
{
// Create a mod path from a url
Uri path = new Uri("trivialinteractive.co.uk/Mods/ExampleMod.umod");
// Issue a mod load request
host = Mod.loadMod(path);
// Add an event listener for progress updates
host.onModLoadProgress += onModLoadProgress;
}
private void onModLoadProgress(ModLoadProgressArgs args)
{
// This will be true if the host is downloading from a server
if(args.IsDownloading == true)
{
Debug.Log("Download Progress: " + args.Progress);
Debug.Log("Download Speed: " + args.Speed);
}
卸载Mod
成功实现mod加载后,您可能需要做的下一件事是卸载mod,这也是一项琐碎的任务。 开发人员随时可以卸载Mod认为合适,将导致主机丢弃所有与mod相关的数据,从而产生一个干净的主机实例,可能会重复使用。
注意:由于CLR的限制,任何已加载到内存中的mod脚本都可能保留但是,在主机卸载了mod之后,它们将被视为无效脚本,并且将不会收到任何脚本。来自主机的事件或上下文数据。
eg:
using System;
using UnityEngine;
using UMod;
public class ModUnloadExample:MonoBehaviour{
private ModHost host = null;
private void Start(){
Uri path = new Uri("file:///C:/Mods/TestMod.umod");
host = Mod.Load(path);
if(host.IsLoaded)
{
host.unloadMod;
}
}
}
注意:如果您卸载的主机是另一个mod主机的依赖项,则该mod也将是卸载以防止依赖关系链被破坏。
您可能已经知道,mod主机对象源自单声道行为,因此可能会像通常销毁Unity对象一样被销毁。 使用销毁主机Object.Destroy确实会导致托管mod像您期望的那样被卸载。
Mod References
从2.1.5版本开始,uMod支持mod引用,这允许mod引用一个或多个其他mod。 添加对mod的引用可使所有资产场景和公共脚本可供访问parent mod意味着您可以共享公共资产场景和代码,而不是存储在多个模组中具有相同的内容。所有mod引用都是在modder导出时定义的,如果没有它们就无法更改重建mod。 查看modder文档,了解有关添加和添加的信息。管理mod参考。
using UnityEngine;
using UMod;
public class ModReferenceResolveExample:MonoBehaviour{
private void Start()
{
Mod.OnResolveReference += OnResolvePath;
}
private Uri OnResolvePath(IModNameInfo reference)
{
string path = Path.Combine("C:/Mods", reference.ModName + ".umod");
return new Uri(path);
}
}
注意:只有在uMod无法找到参考的情况下,才会触发“ OnReferenceResolve”事件。 它不用作完整的替换解析器。
using UMod;
public class ModReferenceResolveExample : MonoBehaviour
{
private void Start()
{
// Find an installed mod
IModInfo info = ModDirectory.GetMod("TestMod");
// Iterate all references
foreach(IModNameInfo referenceInfo in info.ReferenceInfo)
{
// Print the name and version
Debug.Log(referenceInfo.ModName + ", " + referenceInfo.ModVersion);
}
}
}
一旦加载了mod,还可以访问依赖于其的mod主机数组在第一个主机上。 然后,您可以直接从主机访问参考信息依赖关系如下例所示:
using UnityEngine;
using UMod;
public class ModReferenceResolveExample : MonoBehaviour
{
private void Start()
{
// Iterate all references for the host
foreach(IModNameInfo referenceInfo in host.ReferencedMods)
{
// Print the name and version
Debug.Log(referenceInfo.ModName + ", " + referenceInfo.ModVersion);
}
// Iterate all host dependencies
foreach(ModHost referenceHost in host.HostDependencies)
{
// Get the mod info
IModInfo referenceInfo = referenceHost.CurrentMod;
// Print the name and version
Debug.Log(referenceInfo.NameInfo.ModName + ", " + referenceInfo.NameInfo.ModVersion);
}
}
}
using UnityEngine;
using UMod;
public class ModReferenceAssetsExample : MonoBehaviour
{
private void Start()
{
// We assume this host is already valid and loaded
ModHost host;
// Load an asset which is located in a referenced mod
host.ModSharedAssets.Load(“Cube”);
}
}
“ ModSharedAssets”属性返回的“ IModAssets”界面仅用于加载资产包含在当前mod中。 请查看“ Mod资产”部分,以获取有关以下内容的更多信息用法.
Mod Assets
uMod 2.0允许将任何Unity资产类型包含在mod中,并且这些资产可供游戏在运行时。 作为开发人员,您可以通过类似的方式从mod加载资源您将使用Unity中的“资源”文件夹。 以下各节将介绍资产的装载以及任何特殊要求.
Load Mod Assets
根据您要支持的改装类型,您需要能够加载成功加载mod后的资产数量。 uMod 2.0允许您在类似于Unity中“资源”文件夹的方法,并且可以返回源自“ UnityEngine.Object”。就像资源文件夹一样,您可以使用资产名称来加载资产。 常用的方法修改是为了允许修改者创建具有特殊预定义名称的资产。 加载mod后您搜索与这些名称匹配的资产,然后可以将游戏中的资产替换为修改后的资产。
注意:加载游戏对象时要注意的重要一点是,它们必须像实例一样被“实例化”从“资源”文件夹加载时,您会希望它们出现在场景中。 你应该请特别注意,因为Unity中似乎有一个错误会导致引擎死机。从mod加载的gameobject无需实例化就可以修改。
- 您可以使用三种主要方法从mod加载资源,以及您可以使用的方法选择将取决于您要支持多少个同时加载的mod。对于一些您可能只想一次支持加载单个mod的游戏,这将有助于消除冲突和冲突。对于其他游戏,您可能需要支持任意数量的mod同时运行,在这种情况下,您将采用不同的方法。
- ModAssets:“ ModAssets”是静态类,开发人员可以在运行时从中使用它来加载资产。通常,只有一次要支持多个加载的mod时,才选择此选项,因为它将在所有加载的mod中搜索具有指定名称的资产。
- Assets:另一种方法是使用适当的mod主机的’Assets’属性,以访问与上述方法相同的加载API。在访问此API之前,请确保主机已加载mod,这一点很重要,否则将引发异常。
- SharedAssets:访问Mod资产的最终方法是使用相应Mod主机的“ Sharedassets”属性。共享资产包含mod中包含的所有资产以及递归引用的所有mod资产。这意味着将可以访问任何引用的mod或间接引用的mod中的任何资产。在访问此API之前,请确保host已加载mod,这一点很重要,否则将引发异常。
注意:您可以使用相同的资产名称进行任意数量的加载调用,并且该资产将是缓存在第一个调用上,以防止在不必要时重新加载。
- 资产API中的三个主要调用具有多个重载,以提供与“资源”类相似的行为。以下所有方法都将允许资产缓存,这意味着对具有相同资产名称的load方法的多次调用将导致仅在第一次调用时进行加载。第二个调用将仅返回一个缓存的实例。
- Load:所有加载方法都将尝试从具有指定名称的mod加载资产。这些调用将导致加载在主线程上进行,任何加载都会阻塞,直到它们完成为止。返回值将始终为“ UnityEngine.Object”类型或诸如“ Texture2D”之类的派生类型,并且您可以使用通用重载来指定返回值的类型。
- LoadAsync:所有LoadAsync方法将具有与“ load”方法相同的功能,但是,加载将在允许主线程执行的后台线程上进行继续执行。结果,异步方法返回一个可屈服的ModAsyncOperation对象可用于访问加载进度信息。
- Instantiate:从mod加载游戏对象时,返回值必须为实例化以使其出现在场景中。可以通过随后的调用来完成“ Object.Instantiate”,或者您可以使用此“ instantiate”方法来加载资产预先。
以下C#代码显示了资产API的基本用法。 有关更详细的示例,请看一看在软件包随附的示例脚本中。 以下代码假定一个mod具有为了使代码简洁,已经加载了。
Load
using UnityEngine;
using UMod;
public class UModAssetExample : MonoBehaviour
{
private ModHost host = null;
private void Start()
{
// This example assumes that the 'host' already has a valid mod loaded.
if(host.Assets.Exists("MyAsset") == true)
{
// Load the asset from the mod
GameObject go = host.Assets.Load("MyAsset") as GameObject;
// Create an instance in the scene
Instantiate(go, Vector3.zero, Quaternion.identity);
}
}
}
Async Asset Loading
uMod 2.0包含一个自定义的yield指令,用于从称为mod的mod加载资产“ WaitForAssetLoad”。 创建该实例的实例后,将立即发出该加载调用指令,因此您不应该缓存对类型的引用,而应为每个加载请求。 以下C#代码显示了一个基本示例,其中协程将产生直到资产已加载。
using UnityEngine;
using UMod;
public class ModAsyncAssetExample : MonoBehaviour
{
private ModHost host = null;
private IEnumerator Start()
{
// This example assumes that the 'host' already has a valid mod loaded.
// Create a request
ModAsyncOperation request = host.Assets.LoadAsync("MyAsset");
// Wait for the task to complete
yield return request;
// Get the result as a game object
GameObject go = request.Result as GameObject;
// Create an instance in the scene
Instantiate(go, Vector3.zero, Quaternion.identity);
}
}
注意:实际加载将大部分在后台线程上完成,因此您可以在处理请求时运行其他任务。
>> 有关更多信息,请查看随附的示例脚本,该脚本显示了异步资产加载细节。
Unloading Assets
一旦Mod主机加载了资产,它们就被认为是该主机所拥有,并且只有在卸载主机后才能将其卸载。 通过在mod主机上调用“ unloadMod”,所有已加载mod的资产也将被卸载。 这仅适用于诸如纹理或预制件之类的共享资产引用,但是可以通过使用“ Object.Destroy”方法,如您在Unity中所期望的那样销毁任何实例化的资产。
Shared Assets
uMod 2.0支持称为资产共享的概念,其中可以共享游戏中包含的资产带有加载的mod,允许modders访问游戏资产。 通常,modder将使用特殊的游戏对象称为预制节点,用以标记游戏场景中的位置和方向应该放置资产,并且在运行时将根据资产名称解析此引用。 一种一个很好的例子是在修改的场景中标记玩家角色的开始位置。作为开发人员,您将始终完全控制modders可以访问和不能访问的内容因此,您必须明确选择允许与mod共享的资产。 新增中或删除共享资产可以通过“修改资产”窗口完成,该窗口可以从设置窗口。 在资产标签上,有一个名为“编辑资产”的按钮,点击此按钮后,
Mod Scripting
uMod 2.0允许在运行时创建和使用包含C#脚本的mod。 这些脚本将通常以与传统Unity脚本相同的方式执行,但可能有一些事情不同的。 本节将介绍uMod 2.0的整个脚本系统及其工作方式。
Requirements
该脚本系统只能在uMod 2.0的完整版本中使用,并且在以下版本中已完全禁用试用版意味着附加到预制件的脚本引用可能无法正确加载,因为托管程序集永远不会加载。
Basic Scripting Support
uMod 2.0提供了开箱即用的基本脚本支持,使修改后的脚本可以无需执行任何操作即可自动加载并执行。 为了做到这一点,uMod为modder提供了一个接口,可用于创建将要创建的入口点脚本。在加载mod时初始化。
Concepts
下一节将介绍uMod 2.0脚本系统使用的关键概念。 这些概念相当简单,但在系统中起着关键作用。
Script Domain
uMod 2.0使用了脚本域的概念,您可以将其视为任何对象的容器外部加载的代码。 如果mod包含任何代码,则将创建一个脚本域在加载过程中自动进行。 创建此域后,所有修改后的代码都将在没有安全性的情况下,将自动进行安全性检查并将其加载到域中错误。 您可以访问mod的脚本域,如下所示:
using UnityEngine;
using UMod;
// Required for access to the scripting api
using UMod.Scripting;
public class Example : MonoBehaviour
{
ModHost host;
void Start()
{
// This example assumes that 'host' has been initialized
and has a mod loaded
ScriptDomain domain = host.ScriptDomain;
// Print the name of each assembly
foreach(ScriptAssembly assembly in domain.Assemblies)
Debug.Log(assembly.Name);
}
}
如果您特别擅长C#,那么您可能会熟悉**“ AppDomains”**。 值得注意的是uMod 2.0不会为mod脚本创建单独的AppDomain,而是将所有代码加载到当前域。 这是由于.Net的单一实施存在许多限制结果是,一旦加载了mod代码,直到游戏退出之前,都无法卸载mod代码。 这是但这不是问题,因为一旦卸载了mod,代码将在内存中处于空闲状态,并且将不再使用。 通常,mod将在启动时加载一次,并且可以一直运行到游戏退出所以这个限制不是真正的问题。
脚本域只是充当过滤器,仅允许外部代码对用户可见,而不是用户可见所有已加载代码(包括游戏代码)的代码。 除了充当容器外,脚本域也是负责加载C#代码或程序集,并进行安全验证以确保任何加载的代码不使用潜在危险的程序集或名称空间。 例如,通过默认不允许访问“ System.IO”。
Script Assembly
脚本程序集是托管程序集的包装器类,其中包括许多用于查找符合特定条件的脚本类型。 例如,查找继承自的类型UnityEngine.Object。
为了获得对脚本程序集的引用,您将需要使用“ LoadAssembly”之一Script Domain类的方法。 根据设置,脚本域也可能会验证加载之前,请确保代码没有非法引用的程序集或名称空间。
脚本程序集还公开了一个名为“ MainType”的属性,该属性对于外部仅定义一个类的代码。 对于包含多个类型的程序集,MainType将是该程序集中的第一个定义的类型。
如果您需要对程序集的更多控制,则可以使用“ RawAssembly”属性来访问脚本程序集正在管理的“ System.Reflection.Assembly”。 如果您这样做会很有用需要访问脚本程序集中未公开的其他程序集信息。注意:在运行时加载到脚本域中的所有程序集或脚本都将保留,直到应用程序结束。 由于受管运行时的限制,任何已加载的程序集都无法卸载。
Script Type
脚本类型充当“ System.Type”的包装类,并具有许多单位特定的属性以及使管理外部代码更加容易的方法。 例如,您可以使用该属性称为“ IsMonoBehaviour”,以确定类型是否从MonoBehaviour继承。
脚本类型的主要优点是,它提供了许多针对特定于类型的方法构造意味着将始终使用正确的方法来创建类型。
* 对于从MonoBehaviour继承的类型,脚本类型将要求GameObject为传递,并将使用“ AddComponent”方法创建该类型的实例。
* 对于从ScriptableObject继承的类型,脚本类型将使用“ CreateInstance”创建类型实例的方法。
* 对于普通的C#类型,脚本类型将使用基于以下内容的适当构造函数根据提供的参数(如果有)。
这种抽象使为外部代码创建通用加载系统变得更加容易。
Script Proxy
脚本代理用于表示使用以下其中一种创建的脚本类型的实例“ CreateInstance”方法。 脚本代理是通用包装器,可以包装Unity实例例如MonoBehaviours组件以及普通的C#实例.
脚本代理的主要用途之一是通过调用方法或访问mod脚本的字段和属性。 这种通讯是可能的,而无需知道通讯的类型是基于字符串的,因此是修改后的代码的类型。 这只是意味着为了调用方法,您将名称指定为字符串值,而不是将方法调用为你通常会的。 如果您熟悉unity的“ SendMessage”功能,那么您将立于不败之地家。 有关Mod通信的更多信息,请查看“接口方法”本文档后面的小节。
脚本代理还实现了IDisposable接口,该接口可处理实例根据其类型自动生成。 再次这样做是为了易于使用和统一的方法销毁脚本。
- 对于从MonoBehaviour继承的实例,脚本代理将在实例以删除组件
- 例如,从ScriptableObject继承,脚本代理将在实例销毁数据
- 对于普通的C#实例,脚本代理将释放对包装对象的所有引用允许垃圾回收器回收内存
注意:您无需在脚本代理上调用“处置”。 它被简单地包括在内以提供一种通用的,类型无关的销毁方法。
Executing Assemblies
为了实现脚本支持,您可能需要在某个时间点(可能是在加载之后)找到所有正在执行的脚本,以便可以调用事件方法或类似方法。 到加载mod时,取决于mod内容和各种设置,可能已经有许多执行脚本。 您可以随时通过访问mod的执行上下文来访问正在运行的脚本:
using UnityEngine;
using UMod;
// Required for access to the scripting api
using UMod.Scripting;
public class Example : MonoBehaviour
{
// This example assumes that 'host' has been initialized and loaded.
ModHost host;
void Start()
{
// Get the exection context for the mod
ScriptExecutionContext context = host.ScriptDomain.ExecutionContext;
}
}
执行上下文可以更好地控制脚本,还可以使您获得以下内容的数组:当前正在执行的脚本作为脚本代理。 您还可以“杀死”执行上下文将立即销毁所有正在运行的mod脚本实例,并确保触发必要时进行“ OnModUnloaded”事件。 如果您不希望执行Mod代码,这可能会很有用还有更多,但仍想访问该Mod的资产内容。
Loading Assemblies
可以随时将托管程序集加载到脚本域中,但请注意,任何代码mod中包含的内容将自动进行安全检查并加载。本部分仅适用于未直接封装在mod中的外部代码。有许多加载方法提供使您能够实现此目标的方法,所有这些方法都将执行附加的安全验证检查是否启用。所有这些方法都直接在Script Domain实例上调用,可以从加载mod的“ ModHost”中访问。
程序集的加载方法如下:
- **LoadAssembly(字符串):**尝试从指定的文件路径加载托管程序集。
- **LoadAssembly(AssemblyName):**尝试以指定的方式加载托管程序集程序集名称。请注意,由于限制,此方法无法进行安全检查代码,因此建议可以使用其他“加载”方法。
- **LoadAssembly(byte []):**尝试从其原始字节数据中加载托管程序集。这是如果程序集已经在内存中或正在从远程下载它,则很有用来源或类似内容。
- **LoadAssemblyFromResources(string):**这是Unity特定的加载方法,任何人都可以尝试
从资源文件夹中的指定TextAsset加载程序集。所有这些加载方法都返回一个脚本集,该脚本集可用于通过以下方式访问脚本类型:“查找”方法的数量。有关加载方法的更多信息,请查看随附的单独的API文档。与包装。
Activation
您可能已经知道uMod包含一个基本的改装接口,供改装者使用提供适当的回调,例如“ OnModLoaded”和“ OnModUnloaded”。 这个界面也允许修改器执行许多其他有用的事情,例如加载资产,请求场景更改以及多得多。 激活被定义为初始化使用此接口的mod类型的过程,因此他们收到了预期的回拨。 默认情况下,mod中包含的任何代码为自动激活,允许mod代码在加载mod后立即运行,但是可以从另一个来源加载外部代码并激活其中的类型非常有用。 你是能够随时使用uMod脚本API手动激活类型。 下面的例子显示了如何激活外部托管程序集:
using UnityEngine;
using UMod;
// Required for access to the scripting api
using UMod.Scripting;
public class Example : MonoBehaviour
{
// This example assumes that 'host' has been initialized and loaded.
ModHost host;
void Start()
{
// Get the exection context for the mod
ScriptExecutionContext context = host.ScriptDomain.ExecutionContext;
// Load an assembly from a file path
ScriptAssembly assembly = host.ScriptDomain.LoadAssembly("C:/Examples/Example.dll");
// Activate the assembly
ScriptProxy[] proxies = context.ActivateAssembly(assembly);
// All of these proxies have now been activated and are receiving mod events
foreach(ScriptProxy proxy in proxies)
Debug.Log(proxy.ScriptType);
}
}
Interface Approaches
将程序集或脚本加载到脚本域并创建脚本代理后例如,您可能要采取的下一步是以某种方式与类型进行通信如果您想在用户模块中支持脚本,那么uMod 2.0可以让您相对轻松地做到这一点。在实际上,所有的辛苦工作已经完成,您不需要为基本脚本支持做任何事情。经过默认的uMod将在导出过程中编译脚本并将其构建到mod中,这将是安全的在运行时由uMod检查,加载和激活。这意味着任何使用uMod的脚本界面将接收所有mod事件,例如“ OnModLoaded”,以及所有单声道行为mod实例化mod对象时将创建脚本。在某些情况下,这可能就足够了脚本支持,但是对于有意义的脚本支持您将不可避免地需要在游戏和模组之间进行交流。这需求引入了许多问题,即mod如何知道什么类型的游戏代码中的可用内容以及如何使用它们。 uMod 2.0有2种主要的交叉方法可以根据需要统一使用的通信。这些通信类型称为使用基于字符串的类型交互的“通用通信”和“接口通信”开发人员在其中提供了一个附加的界面程序集,其中描述了可以使用的类型通过modder。
Generic Communication
泛型沟通被视为一种非具体的沟通方式,这意味着要与之通信的类型在编译时未知。这带来了一些问题,因为您无法简单地调用未知类型的方法。幸运的是,uMod 2.0包含一个基本的通用通信系统,使用反射进行工作,并允许任何类成员成为在不知道运行时类型的情况下进行访问。但是,此方法确实需要您知道您可以与之通信的类型和/或成员的名称。在这一点上,这取决于开发人员提供合适的改装文档,概述名称或类型和成员可以与此通信方法一起使用。例如,Unity提供了有关以下方面的文档:单一行为类的“魔术”事件,例如“更新”或“开始”。脚本代理用于使用字符串标识符与外部代码进行通信以访问成员。
以下示例显示如何创建自己的魔术方法类型事件:
using UnityEngine;
using DynamicCSharp;
public class Example : MonoBehaviour
{
// This example assumes that 'proxy' is created before hand ScriptProxy proxy;
void Start()
{
// Call the method 'OnScriptStart'
proxy.SafeCall("OnScriptStart");
}
void Update()
{
// Call the method 'OnScriptUpdate'
proxy.SafeCall("OnScriptUpdate");
}
}
上面的代码显示了如何使用运行时在运行时调用具有指定名称的方法。
“ SafeCall”方法。可以使用以下方法来调用外部脚本上的方法:
* 调用:调用方法将尝试使用指定名称并在发生错误时调用方法会抛出异常。由于调用目标方法而引发的任何异常也将无法处理并传递到调用堆栈,因此建议您使用除非您想实施自己的错误处理,否则请选择“ SafeCall”。
* SafeCall:SafeCall方法本质上是“ Call”方法的包装,并处理任何它引发的异常。如果找不到目标方法,则此方法将以静默方式失败。
调用方法时,将参数传递给该方法也非常有用。 uMod 2.0允许传递任意数量的参数,只要传递的类型对于游戏和外部脚本。这种Unity类型的理想选择,例如Vector3或基本类型,例如int,string和float。目标方法还必须接受相同的参数列表否则,调用该方法将失败。
uMod 2.0还包括一种访问外部脚本的字段和属性的方式,前提是他们的名字是事先知道的。 再次通过代理实现通信,但不是调用方法时,您可以使用代理的“字段”或“属性”属性。
- Fields
只要指定的类型与字段相匹配,就可以读取或写入字段的值字段类型。 如果类型不匹配,则可能会引发类型不匹配异常。与方法不同,没有使用该方法访问字段的安全选择。 如果你希望在访问字段时保持安全,那么您应该捕获所有引发的异常。以下代码显示了如何修改名为“ testField”的字段:
using UnityEngine;
using DynamicCSharp;
public class Example : MonoBehaviour
{
// This example assumes that 'proxy' is created before hand ScriptProxy proxy;
void Start()
{
// This example assumes that 'testField' is an int
// Read the value of the field int old = proxy.Fields["testField"];
// Print to console
Debug.Log(old);
// Set the value of the field
proxy.Fields["testField"] = 123;
}
}
- Properties
属性与字段略有不同,因为实现get不需要它们和设置方法。 这意味着某些属性无法写入或读取这意味着您必须更加小心。
如果您尝试从不支持该属性的属性读取或写入该属性,则该目标可能会引发异常。与字段一样,没有安全的替代方法可以访问特性。 如果您想在访问属性时保持安全,则应该抓住任何引发异常。
以下代码显示了如何访问名为“ testProperty”的属性。 方法与字段和属性非常相似。using UnityEngine; using DynamicCSharp; public class Example : MonoBehaviour { // This example assumes that 'proxy' is created before hand ScriptProxy proxy; void Start() { // This example assumes that 'testProperty' is an int // Read the value of the property int old = proxy.Properties["testProperty"]; // Print to console Debug.Log(old); // Set the value of the property proxy.Properties["testProperty"] = 456; } }
Interface Communication
第二种通信方法比以前的通信方法先进得多,但是它与基于松散字符串的通信相反,它将允许进行具体类型的通信。 它还应提供改进的运行时性能,因为它不依赖于对访问类型的反射和成员。
该实现涉及创建一个共享接口,该接口包含任意数量的基类或所有外部代码都必须继承的C#接口。 最好的方法是创建一个包含这些共享基本类型的单独托管程序集,并使其可用于修改后的代码。 为了确保修改者可以访问此共享程序集,建议您将创建自己的包含此程序集的修改后的导出包。 通过这样做,任何由modder创建的脚本将自动引用此程序集。
提供用于创建单独的接口组件的指南不在此范围内文档,但是在线上有许多非常有用的Unity特定教程可以涵盖这。 但是,您应确保程序集将.Net 3.5框架定位为更高版本将导致抛出“ TypeLoadExceptions”。
定义接口后,便可以像调用外部代码一样加载和调用外部代码是您游戏的一部分。作为示例,我们将使用以下C#接口显示该过程的工作方式:
using UnityEngine;
public interface IExampleBase
{
void SayHello();
void SayGoodbye();
}
如您所见,该接口包含2个必须实现的方法,现在我们将假定此接口是在名为“ ExampleInterface.dll”的程序集中定义的。 如上所述之前,游戏代码和mod代码都必须可以访问此程序集,这意味着您应该将此接口分发给modders。
现在,我们将需要修改后的代码来实现此接口,以便我们将其加载到游戏。 如果不是,那么我们将简单地忽略代码,并假设它是无关紧要的或已由uMod自动激活。 我们的示例mod代码如下所示:
using UnityEngine;
public class Test: IExampleBase
{
void SayHello()
{
Debug.Log("Hello");
}
void SayGoodbye()
{
Debug.Log("Goodbye");
}
}
如您所见,示例代码是非常基本的,并且当其中之一出现时,它将简单地打印到Unity控制台它的两种方法被调用。
此时,我们现在假设我们已经使用uMod 2.0导出器。 然后,下一步是识别并加载修改后的代码,以便我们可以调用游戏代码中的“ SayHello”和“ SayGoodbye”方法。 由于该代码已包含在修改后,uMod 2.0会在加载时自动将其加载,这意味着程序集已经加载。 结果,我们可以访问内存中的程序集而不是手动加载。 以下C#代码显示了我们如何访问从我们的继承的所有类型我们之前创建的“ IExampleBase”界面。
using UnityEngine;
using UMod;
// Required for access to the scripting api
using UMod.Scripting;
public class Example : MonoBehaviour
{
// This example assumes that 'host' has been initialized and loaded.
ModHost host;
void Start()
{
// Look through all assemblies loaded in the mod domain
foreach(ScriptAssembly assembly in host.ScriptDomain.Assemblies)
{
// This method will find all types in the assembly that implement our interface
ScriptType[] types = assembly.FindAllSubtypesOf<IExampleBase>();
}
}
}
如您所见,我们现在有一个脚本类型数组,所有这些脚本类型都实现了我们的“ IExampleBase”界面。 这意味着我们可以确保所有这些类型都实现了接口,因此我们可以做的下一个任务是在每种类型上调用这些方法:
using UnityEngine;
using UMod;
// Required for access to the scripting api
using UMod.Scripting;
public class Example : MonoBehaviour
{
// This example assumes that 'host' has been initialized and loaded.
ModHost host;
void Start()
{
// Look through all assemblies loaded in the mod domain
foreach(ScriptAssembly assembly in host.ScriptDomain.Assemblies)
{
// This method will find all types in the assembly that implement our interface
ScriptType[] types = assembly.FindAllSubtypesOf<IExampleBase>();
// Create an instance of all types
foreach(ScriptType type in types)
{
// Create a raw instance of our type
IExampleBase instance = type.CreateRawInstance<IExampleBase>();
// Call its methods as you would expect
instance.SayHello();
instance.SayGoodbye();
}
}
}
}
如您所料,在使用类型之前,我们需要创建它的一个实例使用脚本类型的“ CreateRawInstance”。 该方法在以下方面的主要区别与“ CreateInstance”方法相比,返回的是具体类型,而不是管理脚本代理。 这意味着我们可以直接将结果作为我们的“ IExampleBase”界面,转换将正常进行。 之后,我们现在有了“ Test”类的实例之前定义为“ IExampleBase”接口的存储,这意味着我们现在可以调用方法直接地。
尽管界面方法需要更多设置才能开始工作,但值得您付出努力与代理通信相比,具有类型安全性和额外的性能方法。 这是由于以下事实:代理人依靠引擎盖下的反射来称呼方法和访问成员,这总是比简单地调用方法慢。