エディタスクリプティング
UdonSharpには、通常のC#版のビヘイビアを扱うのと同様に、UdonSharpBehavioursとやり取りを行うカスタムエディタやエディタスクリプトを作成できるエディタスクリプティングAPIが用意されています。
エディタスクリプティングAPIを使用すれば、基本的にはC#版のスクリプトと同じ感覚でC#コードを書くことができますが、作業にあたってはいくつか注意すべき点があります。
U#のエディタスクリプティングの基本として、UdonSharpは裏側でビヘイビアスクリプトのC#版インスタンスを作成し、UdonBehaviour版スクリプトからC#のproxy_へとフィールドをコピーします。エディタスクリプトから操作を行う際、そのほとんどがこのproxy_に対するものとなります。
proxy_
UdonSharpスクリプトは有効なC#コードであるため、通常のスタンドアロンコンポーネントと同様に、GameObjectへproxy_として追加できます。proxy_はUnityのSerializedObject型に似ており、SerializedObjectに加えた変更は元のオブジェクトに適用する必要があり、逆に元のオブジェクトに加えられた変更もシリアライズされたオブジェクト側で更新する必要があります。
プロキシは、対応する UdonBehaviour と同じ GameObject 上に作成され、無効化された状態で存在します。Unity がそれらのイベントを実行しないよう、常に無効の状態を保つ必要があります。プロキシは GameObject のインスペクター上で非表示に設定されているため、GameObject から直接確認することはできませんが、実際には存在しています。また、プロキシにはシーンやビルドに保存されないようフラグが立てられているため、エディタ上でしか存在せず、パッケージやダウンロードサイズを肥大化させる心配はありません。
プロキシに対してメソッドを実行すれば、UdonBehaviour でイベントを実行するのと同様に動作します。留意しておくべき重要な点として、C# において UdonSharpBehaviour は UdonBehaviour ではないということがあります。これについては以下で詳しく説明します。
通常の C# ビヘイビアや U# との違い
UdonSharpBehaviour は UdonBehaviour ではありません
UdonSharpコード内では、内部的に同じオブジェクトとして表現されているため、UdonBehaviourをUdonSharpBehaviourとして扱うことが可能です。技術的には、UdonSharpBehaviourはUdonBehaviourを直接継承しているわけではないため、UdonSharpで作業する際は変数の型としてUdonBehaviourではなくUdonSharpBehaviourを使用するのがベストです。
そのため、グラフベースで作成されたUdonビヘイビアをユーザーが組み込めるようにしたい場合を除き、常にUdonBehaviourではなくUdonSharpBehaviourを型として使用することをお勧めします。
proxy_参照は、UdonSharpBehaviour型の変数に対してのみ自動的に処理されます。
UdonSharpBehaviourの変数型のみ、proxy_が自動的に処理されます。proxy_内で別のproxy_を参照する場合、その参照はproxy_システムによって自動的にUdonBehaviourへの参照に変換されます。そのため、他のUdonSharpBehaviourを参照する変数は、特定のUdonSharpBehaviour型、あるいは任意のUdonSharpBehaviour型を格納したい場合は基底クラスであるUdonSharpBehaviour型として保持するようにしてください。
型としてUdonBehaviourを使用したり、単なるComponent参照を格納したりするなど、曖昧な参照を行う場合、proxy_システムは単にその下位にあるUdonBehaviourへの参照を割り当てます。これは、グラフベースのUdonアセットはproxy_APIの対象外であるため、それらの参照を許可したい場合には有効です。
留意しておくべき重要な点として、UdonSharpBehaviourやそのサブクラスではない変数にproxy_の参照を直接格納した場合、ビルド時にその参照はnullにクリアされてしまいます。例えば、Componentの参照を持たせたい場合は、プロキシであるUdonBehaviourを参照するようにしてください。proxy_そのものを参照してはいけません。
proxy_は常に無効化されており、そのまま無効にしておく必要があります。
proxy_が無効化されるのは、ゲームプレイ中にUnityがイベントを呼び出したり、同じロジックを二重に実行したりすることを防ぐためです。proxy_を再度有効にすることは絶対に避けてください。proxy_は無効化されているため、無効なビヘイビアも取得するように指定せずにGetComponentInChildrenのようなメソッドをproxy_上で実行しても、プロキシは返されません。
UdonSharpBehaviour用のカスタムインスペクターの作成
UdonSharpBehaviour用のカスタムエディターを作成する際、ほとんどの要素は抽象化されているため、通常のカスタムインスペクターを作成するのと全く同じ感覚で行えます。Editorを継承したクラスを作成し、対象となるUdonSharpBehaviourの型を指定したCustomEditor属性を追加するだけです。
U#またはC#スクリプトのカスタムエディタを作成する際は、エディタ用のコードがゲームビルドに含まれないようにしてください。これは、Unityのゲームビルドでは許可されていないEditorライブラリを使用するためです(含まれている場合、ワールドのビルドに失敗します)。
ワールドからエディタ用のコードを除外する推奨方法は2つあります。
- インスペクタスクリプトを 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
COMPILER_UDONSHARP プリプロセッサ定義が true になるのは、UdonSharpBehaviour と同じスクリプト内にある場合のみです。UdonSharpBehaviour を含まず、UdonSharpProgramAsset にも接続されていない外部スクリプトでは、COMPILER_UDONSHARP が true に設定されることはありません。
UdonSharpBehaviour に対して、COMPILER_UDONSHARP や UNITY_EDITOR を使用してフィールドを条件付きで削除または追加しないでください。予期しない動作を引き起こす原因となります。
カスタムインスペクターを作成する際は、必ず OnInspectorGUI を以下のように開始してください。
if (UdonSharpGUI.DrawDefaultUdonSharpBehaviourHeader(target)) return;
これにより、C# スクリプト用の「Behaviour に変換」ボタン、同期設定、インタラクト設定、およびユーティリティを含む、デフォルトの UdonSharp ヘッダーの描画が処理されます。各セクションを個別に描画することも可能です。何が描画できるかについては、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 の作成と同様に機能し、ほとんどの処理は自動的に行われます。エディター上で OnSceneGUI イベントを使用するだけで、期待通りに動作するはずです。
使用する Gizmos
Gizmosが期待通りに動作するには、少し特別な処理が必要です。サンプルスクリプトの一つがGizmosを使用しています。Gizmosを使用する場合は、エディタを囲ったものと同様の #if !COMPILER_UDONSHARP && UNITY_EDITOR チェックで OnDrawGizmos および OnDrawGizmosSelected イベント自体を囲う必要があり、これらのイベントはビヘイビア自身に記述してください。
Gizmosは、proxy_を使用して描画されます。これは、UdonSharpProgramAssetsを持つすべてのUdonBehaviourにアタッチされます。Udonはプレイモード以外ではUdonBehaviourを実行しないため、これらはUdonを通じて実行されるわけではありません。GizmosイベントはUdonSharpによって管理されていないため、proxy_が最新の状態であることを保証するには少し工夫が必要です。これを行うには、以下の2つのメソッドのいずれかを呼び出してください。どちらも同じ動作をしますが、UpdateProxy はUnity APIのシリアライズされたオブジェクトをエミュレートするために作成されたものです。
// Call this
UdonSharpEditorUtility.CopyUdonToProxy(this);
// Or this
this.UpdateProxy();
// Do not call both since you'd be doing redundant work
この例については、前述のサンプルスクリプトで確認できます。
インスペクター以外のエディタスクリプト
UdonSharpBehaviourを作成、削除、または変更するエディタスクリプトを作成する場合は、proxy_の更新および変更の適用を自身で管理する必要があります。
UdonSharpBehaviourの追加
UdonSharpBehaviourを新しく追加するには、アタッチしたいGameObjectを取得して AddUdonSharpComponent<T>() を呼び出すだけで簡単に行えます。
GameObject targetGameObject = ... // Get some game object from somewhere here
MyComponentType newComponent = targetGameObject.AddUdonSharpComponent<MyComponentType>();
これで newComponent は、コンポーネント型 MyComponentType の有効なUdonSharpBehaviour proxy_になります。エディタスクリプトから他のC#コンポーネントと同様に操作できます。コンポーネントの作成をUndo(元に戻す)可能にする場合は、代わりに以下を使用してください:
GameObject targetGameObject = ... // Get some game object from somewhere here
MyComponentType newComponent = UdonSharpUndo.AddComponent<MyComponentType>(targetGameObject);
既存のUdonSharpBehaviourの取得
UdonSharpには、GameObjectの拡張メソッドとして定義されたGetComponent(s)に相当するメソッドがあります。エディタスクリプトで作業する際は、GetComponent<T>() を呼び出す代わりに、その同等機能である GetUdonSharpComponent<T>() を呼び出すようにしてください。
GameObjectの子要素にある型 MyComponentType のUdonSharpBehaviourをすべて取得するには、次のように記述します:
GameObject sourceGameObject = ... // Get some game object from somewhere here
MyComponentType[] myComponents = sourceGameObject.GetUdonSharpComponentsInChildren<MyComponentType>();
UdonSharpBehaviourの操作と変更
UdonSharpBehaviourを作成したら、proxy_に対する変更がUdonに反映されるように処理を行う必要があります。
Udon側でビヘイビアに変更があった場合、GetUdonSharpComponent(s)を使用していれば自動的に更新されるため、ビヘイビアも更新されます。ビヘイビアへの参照を保持している場合は、手動で更新する必要があります。これには、ビヘイビアに対してUpdateProxy()を呼び出します。
ビヘイビアを修正した後は、proxy_上の修正内容をUdonに適用しなければなりません。これには、ビヘイビアに対してApplyProxyModifications()を呼び出します。
これはUnityのSerializedObjectを扱うのと同様の考え方です。
MyComponentType myComponent = ...
// We only need to update the proxy if we storing some persistent reference to it
myComponent.UpdateProxy();
// Add 5 to a `float` on our behaviour
myComponent.myFloatField += 5f;
// Apply the changes to myComponent to the Udon copy of it
myComponent.ApplyProxyModifications();
UdonSharpBehaviourの破棄
UdonSharpBehaviourを破棄し、その基盤となっているUdonBehaviourを削除するには、UdonSharpEditorUtility.DestroyImmediate()メソッドを使用してください。
最終更新: