Skip to main content

编辑器脚本

UdonSharp 包含一个编辑器脚本 API,允许您制作与 UdonSharpBehaviour 交互的自定义编辑器和编辑器脚本,就像与正常的 C# 版本的行为交互一样。

大多数情况下,编辑器脚本 API 允许您编写 C# 代码,其工作方式与 C# 版本的脚本相同,但在使用脚本时需要注意一些事项。

U# 编辑器脚本的基础是 UdonSharp 将创建行为脚本的 C# 版本的实例,并在幕后将 UdonBehaviour 版本脚本中的字段复制到 C#"代理"中。在编辑器脚本中,您大多数时候与此代理交互。

代理​

UdonSharp 脚本是有效的 C#,这允许你将其作为代理添加到 GameObject 上,就像普通的独立组件一样。代理类似于 Unity 的 SerializedObject 类型,你对 SerializedObject 应用的任何更改都需要应用到原始对象,反之亦然。

代理与其支持的 UdonBehaviour 创建在同一个 GameObject 上,并且是禁用的。它们应始终保持禁用,以便 Unity 不会在其上执行事件。它们在 GameObject 检查器中被标记为隐藏,因此你不能直接在 GameObject 上看到它们,但它们确实存在。代理还被标记为不在场景中保存且不在构建中保存,因此你无需担心它们会增大包体或下载大小,因为它们仅存在于编辑器中。

你可以在代理上执行方法,它们的工作方式与在 UdonBehaviour 上运行事件相同。你需要记住的主要事情是,在 C# 中 UdonSharpBehaviour 不是 UdonBehaviour,这将在下面进一步详细解释。

与常规 C# behaviour 和 U# 的差异​

UdonSharpBehaviour 不是 UdonBehaviour​

在 UdonSharp 代码中,可以将 UdonBehaviour 视为 UdonSharpBehaviour,因为它们在内部表示为同一个对象。从技术上讲,UdonSharpBehaviour 实际上并不继承自 UdonBehaviour,因此在 UdonSharp 中工作时,最好使用 UdonSharpBehaviour 作为变量类型而不是 UdonBehaviour。

因此,除非你想允许他人使用从 Graph 构建的 Udon behaviour,否则应始终优先使用 UdonSharpBehaviour 作为变量类型而不是 UdonBehaviour。

代理引用仅对 UdonSharpBehaviour 变量自动处理​

仅 UdonSharpBehaviour 的变量类型会自动处理其代理。当你在代理 behaviour 中引用另一个代理 behaviour 时,其引用将由代理系统自动转换为 UdonBehaviour 引用。因此,引用其他 UdonSharpBehaviour 的变量应存储为特定的 UdonSharpBehaviour 类型,或者如果可以存储任何 UdonSharpBehaviour 类型,则存储为基 UdonSharpBehaviour 类型。

如果你使用 UdonBehaviour 类型的变量、存储普通的 Component 引用或任何不明确的引用,代理系统将填充对底层 UdonBehaviour 的引用。如果你希望允许引用 Graph 资源,这很好,因为它们不属于代理 API。

需要记住的重要一点是,如果你将代理 behaviour 的引用直接存储在不是 UdonSharpBehaviour 变量或 UdonSharpBehaviour 的某个子类的变量中,该引用将在构建时被清除为 null。例如,如果你想要一个 Component 引用,请确保它引用的是 UdonBehaviour 而不是代理 UdonSharpBehaviour。

代理始终处于禁用状态,应保持禁用​

代理被禁用以防止 Unity 在游戏过程中调用事件并重复执行业逻辑。你绝不应重新启用代理 behaviour。由于代理 behaviour 被禁用,如果你在代理 behaviour 上运行方法,调用诸如 GetComponentInChildren 之类的方法时,如果不告诉它也获取禁用的 behaviour,它将不会返回代理。

为 UdonSharpBehaviour 创建自定义检查器​

为 UdonSharpBehaviour 创建自定义编辑器时,大多数内容都被抽象化了,几乎与创建普通的自定义检查器完全相同。你只需要创建一个继承自 Editor 的类,并添加带有 UdonSharpBehaviour 类型的 CustomEditor 特性。

为任何 U# 或 C# 脚本创建自定义编辑器时,必须确保你的编辑器代码不包含在游戏构建中,因为它使用了 Unity 游戏中不允许的编辑器库(如果包含,你的世界将无法构建)。

有两种推荐的方法可以从世界中排除编辑器代码:

  • 将检查器脚本放在名为 Editor 的文件夹中。
  • 将代码包装在预处理器定义 UNITY_EDITOR 的检查中,例如:
#if UNITY_EDITOR
[CustomEditor(typeof(CustomInspectorBehaviour))]
public class CustomInspectorEditor : Editor
{
...
}
#endif

像 UnityEditor 这样仅编辑器使用的命名空间的 using 语句也需要包装在相同的 UNITY_EDITOR 检查中

如果你想在与 UdonSharpBehaviour 脚本相同的脚本文件中编写检查器,则需要使用 COMPILER_UDONSHARP 预处理器定义来防止 UdonSharp 解析仅编辑器代码。使用上述示例,如下所示:

public class CustomInspectorBehaviour : UdonSharpBehaviour
{
...
}

#if !COMPILER_UDONSHARP && UNITY_EDITOR
[CustomEditor(typeof(CustomInspectorBehaviour))]
public class CustomInspectorEditor : Editor
{
...
}
#endif
warning

COMPILER_UDONSHARP 预处理器定义仅在 UdonSharpBehaviour 所在的同一脚本中为 true。不包含 UdonSharpBehaviour 且未连接到 UdonSharpProgramAsset 的外部脚本永远不会将 COMPILER_UDONSHARP 设置为 true。

warning

不要使用 COMPILER_UDONSHARP 或 UNITY_EDITOR 有条件地删除或添加 UdonSharpBehaviour 的字段。这将导致意外行为

当你创建自定义检查器时,应始终以以下代码开始 OnInspectorGUI:

if (UdonSharpGUI.DrawDefaultUdonSharpBehaviourHeader(target)) return;

这处理了绘制默认 UdonSharp 头部,其中包含 C# 脚本的转换为 behaviour 按钮、同步设置、交互设置和实用工具。你也可以单独绘制每个部分,查看 DrawDefaultUdonSharpBehaviourHeader() 的实现了解可以绘制的内容。

示例检查器​

此示例随 UdonSharp 提供

using UnityEngine;
using VRC.SDK3.Components;
using VRC.SDKBase;
using VRC.Udon;

#if !COMPILER_UDONSHARP && UNITY_EDITOR // These using statements must be wrapped in this check to prevent issues on builds
using UnityEditor;
using UdonSharpEditor;
#endif

namespace UdonSharp.Examples.Inspectors
{
/// <summary>
/// Example behaviour that has a custom inspector
/// </summary>
public class CustomInspectorBehaviour : UdonSharpBehaviour
{
public string stringVal;

private void Update()
{
Debug.Log($"CustomInspectorBehaviour: {stringVal}");
}
}

// Editor scripts must be wrapped in a UNITY_EDITOR check to prevent issues while uploading worlds. The !COMPILER_UDONSHARP check prevents UdonSharp from throwing errors about unsupported code here.
#if !COMPILER_UDONSHARP && UNITY_EDITOR
[CustomEditor(typeof(CustomInspectorBehaviour))]
public class CustomInspectorEditor : Editor
{
public override void OnInspectorGUI()
{
// Draws the default convert to UdonBehaviour button, program asset field, sync settings, etc.
if (UdonSharpGUI.DrawDefaultUdonSharpBehaviourHeader(target)) return;

CustomInspectorBehaviour inspectorBehaviour = (CustomInspectorBehaviour)target;

EditorGUI.BeginChangeCheck();

// A simple string field modification with Undo handling
string newStrVal = EditorGUILayout.TextField("String Val", inspectorBehaviour.stringVal);

if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(inspectorBehaviour, "Modify string val");

inspectorBehaviour.stringVal = newStrVal;
}
}
}
#endif
}

使用 Handles​

这与制作自定义检查器 GUI 的工作方式相同,大多数情况下一切都会自动处理。你只需在 Editor 上使用 OnSceneGUI 事件,它就会按预期工作。

使用 Gizmos​

Gizmos 需要一些特殊处理才能按预期工作。其中一个示例脚本使用了 Gizmos。对于 Gizmos,你应该将 OnDrawGizmos 事件本身包装在与包装编辑器相同的 #if !COMPILER_UDONSHARP && UNITY_EDITOR 检查中,并且 OnDrawGizmos 和 OnDrawGizmosSelected 事件应该放在 behaviour 本身上。

Gizmos 使用代理 behaviour 来绘制,代理 behaviour 会附加到所有带有 UdonSharpProgramAssets 的 UdonBehaviour 上。它们不会通过 Udon 执行,因为 Udon 在非运行模式下不会运行 UdonBehaviour。Gizmos 事件不受 UdonSharp 管理,因此你需要做一些额外工作来确保代理 behaviour 是最新的。为此,可以调用以下两个方法之一,两者的作用相同,UpdateProxy 只是为模拟 Unity 的序列化对象 API 而设。

// 调用这个
UdonSharpEditorUtility.CopyUdonToProxy(this);
// 或者这个
this.UpdateProxy();
// 不要同时调用两者,因为你会做重复工作

你可以在上面链接的示例脚本中看到相关示例。

非检查器编辑器脚本​

当你创建用于创建/删除/修改 UdonSharpBehaviour 的编辑器脚本时,你需要自行管理代理的更新和应用其修改。

添加 UdonSharpBehaviour​

添加新的 UdonSharpBehaviour 非常简单,只需获取你想要附加到的 GameObject,然后在其上调用 AddUdonSharpComponent<T>() 即可。

GameObject targetGameObject = ... // 从某处获取游戏对象
MyComponentType newComponent = targetGameObject.AddUdonSharpComponent<MyComponentType>();

现在 newComponent 是你的组件类型 MyComponentType 的有效 UdonSharpBehaviour 代理。你可以像在编辑器脚本中与任何其他 C# 组件一样与之交互。如果你希望组件创建可撤销,请改用:

GameObject targetGameObject = ... // 从某处获取游戏对象
MyComponentType newComponent = UdonSharpUndo.AddComponent<MyComponentType>(targetGameObject);

获取现有的 UdonSharpBehaviour​

UdonSharp 为 GameObject 定义了与 GetComponent(s) 等效的扩展方法。在编写编辑器脚本时,你应该调用其等效方法 GetUdonSharpComponent<T>(),而不是 GetComponent<T>()。

要获取 GameObject 子对象上所有类型为 MyComponentType 的 UdonSharpBehaviour,你可以这样做:

GameObject sourceGameObject = ... // 从某处获取游戏对象
MyComponentType[] myComponents = sourceGameObject.GetUdonSharpComponentsInChildren<MyComponentType>();

操作 UdonSharpBehaviour 并修改它​

一旦你有了一个 UdonSharpBehaviour 可供操作,你需要确保对代理所做的任何修改都能传播到 Udon。

如果 Udon 正在对 behaviour 进行任何更改,那么如果你使用 GetUdonSharpComponent(s),behaviour 应该会自动更新,因为这些方法会自动更新 behaviour。如果你存储了对 behaviour 的引用,则需要自行更新它。可以通过在 behaviour 上调用 UpdateProxy() 来完成。

修改完 behaviour 后,你必须将代理 behaviour 上的修改应用到 Udon。可以通过在 behaviour 上调用 ApplyProxyModifications() 来完成。

你可以将其视为类似于操作 Unity SerializedObjects。

MyComponentType myComponent = ...
// 仅当我们存储了对它的持久引用时才需要更新代理
myComponent.UpdateProxy();
// 给 behaviour 上的 `float` 字段加 5
myComponent.myFloatField += 5f;
// 将对 myComponent 的更改应用到它的 Udon 副本
myComponent.ApplyProxyModifications();

销毁 UdonSharpBehaviour​

你应该使用 UdonSharpEditorUtility.DestroyImmediate() 方法来销毁 UdonSharpBehaviour 并删除其底层的 UdonBehaviour。