UnityのUI Toolkitでバインド可能なカスタムコントロールを作成する方法です。
Unity 2022.2.19
はじめに
以下の記事のようにVisualElementを継承したクラスを作成すると、独自のコントロールを作ることができます。
しかしこのままでは、以下の記事のようにパスを指定してSerializedProperty
とバインドするということができません。
本記事ではバインド可能なカスタムコントロールの作り方についてまとめます。
作るもの
今回は以下のように、上のTextField
にアセットパスを入力すると、下のObjectField
にそのパスが示すアセットが表示されるコントロールを作ります。
以下のScriptableObject
のInspectorをこのコントロールを使って実装することを本記事のアウトプットとします。
using UnityEngine; [CreateAssetMenu] public sealed class Example : ScriptableObject { public string assetPath; }
コントロールを実装
それでは早速コントロールを実装します。
using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; namespace Examplet.Editor { public sealed class AssetPathField : BindableElement, INotifyValueChanged<string> // BindableElementとINotifyValueChangedを実装する { private readonly ObjectField _objectField; private readonly TextField _textField; private string _value; public AssetPathField() { _textField = new TextField(); _textField.RegisterValueChangedCallback(OnTextFieldValueChanged); Add(_textField); _objectField = new ObjectField(); _objectField.focusable = false; Add(_objectField); } // INotifyValueChanged<string>の実装 // ChangeEventを発生させずに各フィールドの値を更新する public void SetValueWithoutNotify(string newValue) { _value = newValue; _textField.SetValueWithoutNotify(newValue); var asset = AssetDatabase.LoadAssetAtPath<Object>(newValue); _objectField.SetValueWithoutNotify(asset); } // INotifyValueChanged<string>の実装 public string value { get => _value; // setterは以下の二つのケースで呼ばれる // 1. バインド先の値が変更された時 // 2. TextFieldが編集された時 set { if (value == _value) return; // イベントを発生させる using (var evt = ChangeEvent<string>.GetPooled(_value, value)) { evt.target = this; SetValueWithoutNotify(value); SendEvent(evt); } } } private void OnTextFieldValueChanged(ChangeEvent<string> evt) { // TextFieldが編集されたときにはvalueを変更し、ChangeEventを発生させる value = evt.newValue; } public new class UxmlTraits : BindableElement.UxmlTraits // ここもBindableElementに { } public new class UxmlFactory : UxmlFactory<AssetPathField, UxmlTraits> { } } }
説明はコメントに書いた通りですが、ポイントとしてはBindableElement
を継承している点と、INotifyValueChanged
を実装している点です。
この2点を行うことでバインド可能なコントロールを作成することができます。
Inspectorのレイアウトを作成
次にUI Builderを使ってInspectorのレイアウトを表すUXMLファイルを作ります。
先ほど作ったAssetPathField
がUI Builderからも使えるので、これを配置してBinding PathにassetPath
と入力します。
出力されるUXMLは以下の通りです。
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False"> <Examplet.Editor.AssetPathField binding-path="assetPath" /> </ui:UXML>
これをeditor_layout.uxml
という名前で保存しておきます。
CustomEditorを作成
最後にExampleのInspector表示用のスクリプトを以下のように作成します。
using UnityEditor; using UnityEngine; using UnityEngine.UIElements; [CustomEditor(typeof(Example))] public sealed class ExampleEditor : Editor { [SerializeField] private VisualTreeAsset layout; public override VisualElement CreateInspectorGUI() { var container = layout.CloneTree(); return container; } }
動作確認
Projectウィンドウで先ほどスクリプトを選択し、Inspectorから以下のように設定します。
- Layout: 上で作ったeditor_layout.uxmlをアサイン
次にCreate > Example
からScriptableObject
を作成します。
作成したらそれを選択してInspectorを表示すると、正常に動作していることを確認できます。