-
-
Notifications
You must be signed in to change notification settings - Fork 89
Editor Scripting
As of UdonSharp version 0.18.0, an editor scripting API is included that allows you to make custom editors and editor scripts that interact with UdonSharpBehaviours the same as you would interact with the normal C# version of a behaviour.
For the most part the editor scripting API allows you to write C# code that just works the same as it would for the C# version of the script, but there are some things to keep in mind while working with the scripts.
The basis for U# editor scripting is that UdonSharp will create instances of the C# version of your behaviour script and copy the fields from the UdonBehaviour version of the script to the C# 'proxy' behind the scenes. This proxy is what you'll be interacting with most of the time from editor scripts.
When you create an U# script, it is a completely valid C# script in addition to being valid in Udon when compiled with UdonSharp. Since it is a valid C# script, you are able to add it as a regular standalone component on GameObjects. Proxies are the C# version of your script that get added to GameObjects and linked to the Udon version of the script. When working with proxies, they can be though of as similar to Unity's SerializedObject
type, where any changes you apply to the SerializedObject need to be applied to the original object, and any changes to the original object need to be updated on the serialized object.
Proxies are created on the same GameObject as their backing UdonBehaviour and are disabled. They should remain disabled at all times so Unity does not execute events on them. They are marked as hidden in the GameObject inspector so you cannot see them on the GameObject directly, but they are there. Proxies are also flagged to not be saved in the scene and not be saved in builds so you don't have to worry about them bloating package or download size since they only exist in the editor.
You can execute methods on the proxies and have them work the same as running events on UdonBehaviours. The main thing you need to keep in mind is that in C# UdonSharpBehaviours are not UdonBehaviours, this is explained in further detail below.
In UdonSharp code, it is possible to treat UdonBehaviours as UdonSharpBehaviours since they are represented as the same object internally. In C# this is not valid since UdonSharpBehaviours do not inherit from UdonBehaviours right now. This is done to prevent changes to UdonBehaviours by VRChat from breaking UdonSharpBehaviour scripts. This may change in the future when the UdonBehaviour API is more concrete.
Because of this, unless you want to allow people to plug in Udon behaviours built from the graph, you should always prefer to use UdonSharpBehaviour
as variable types instead of UdonBehaviour
.
Only variable types for UdonSharpBehaviours will have their proxies handled automatically. When you reference another proxy behaviour in a proxy behaviour, its reference will automatically be converted to the UdonBehaviour reference by the proxy system. Because of this, variables that reference other UdonSharpBehaviours should be stored as the specific UdonSharpBehaviour type, or as the base UdonSharpBehaviour type if any UdonSharpBehaviour type can be stored in the variable.
If you use variables of the type UdonBehaviour
, or if you store plain Component
references, or anything ambiguous at all, the proxy system will just populate the reference to the underlying UdonBehaviour. This is good if you want to allow references to graph assets since they do not fall under the proxy API since they don't have a C# equivalent.
An important thing to keep in mind is that if you store references to the proxy behaviours directly in a variable that is not an UdonSharpBehaviour variable or some subclass of UdonSharpBehaviour, the reference will get cleared to null on build. For instance, if you want to have an Component
reference, make sure its referencing the UdonBehaviour and not the proxy UdonSharpBehaviour.
Proxies get disabled to prevent Unity from calling events and running the same logic twice during gameplay on them. You should never re-enable the proxy behaviours. Because proxy behaviours are disabled, if you run methods on the proxy behaviour that call things like GetComponentInChildren without telling it to get disabled behaviours as well, it will not return the proxies.
When making custom editors for UdonSharpBehaviours, most things are abstracted to the point that it's exactly like making a normal custom inspector. You just need to make a class that inherits from Editor
and add the CustomEditor
attribute with the type of your UdonSharpBehaviour.
When making any custom editors for C# scripts, not just UdonSharp scripts, you must make sure that the editor code you write will not be part of game builds since it will make worlds fail to build. In Unity you have two main ways to make sure code doesn't get included in the world build. The first is to place your inspector scripts inside a folder named Editor, this will prevent that code from getting included in the build. The other way is to wrap your code in a check for the preprocessor definition UNITY_EDITOR
, for example:
#if UNITY_EDITOR
[CustomEditor(typeof(CustomInspectorBehaviour))]
public class CustomInspectorEditor : Editor
{
...
}
#endif
Using statements for editor only namespaces like UnityEditor
will also need to be wrapped in the same UNITY_EDITOR
check
If you want to write your inspector in the same script file as your UdonSharpBehaviour script, you will need to use the COMPILER_UDONSHARP
preprocessor definition to prevent UdonSharp from parsing the editor only code. Using the above example with this it would look like the following:
public class CustomInspectorBehaviour : UdonSharpBehaviour
{
...
}
#if !COMPILER_UDONSHARP && UNITY_EDITOR
[CustomEditor(typeof(CustomInspectorBehaviour))]
public class CustomInspectorEditor : Editor
{
...
}
#endif
⚠️ TheCOMPILER_UDONSHARP
preprocessor definition will only ever be true inside the same script as the UdonSharpBehaviour, external scripts that do not contain an UdonSharpBehaviour and are not connected to an UdonSharpProgramAsset will never haveCOMPILER_UDONSHARP
marked true
⚠️ Do not useCOMPILER_UDONSHARP
orUNITY_EDITOR
to conditionally remove or add fields to UdonSharpBehaviours, this will result in unexpected behavior
When you make a custom inspector, you should always start the OnInspectorGUI with
if (UdonSharpGUI.DrawDefaultUdonSharpBehaviourHeader(target)) return;
This handles drawing the default UdonSharp header that contains the convert to behaviour button for C# scripts, the syncing settings, the interact settings, and the utilities. You can draw each individual section as well, look at the implementation of DrawDefaultUdonSharpBehaviourHeader()
for what you can draw.
This example is included with 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
}