UnityのUI Toolkitで、SeializedPropertyの値とUIをデータバインディングする方法についてまとめました。
Unity 2021.3.16f1
やりたいこと
いま、SerizliedObjectにシリアライズされているSerializedPropertyの値をUI ToolkitのUIに表示することを考えます。
従来のIMGUIベースの実装方法では、SerializedObject.FindPropertyでSerializedPropertyを取得して、自身でUIの表示を更新する必要がありました。
UI ToolkitではVisualElementにSerializedPropertyのパスを事前に指定しておくことで、対象の値が変わったときに自動的に表示を更新する仕組みがあります。
本記事ではこのデータバインディングの仕組みについてまとめます。
SerializedObjectとバインドする
SerializedObjectとバインドするには以下を行います。
- 対象のUI要素の
bindingPathにプロパティのパスを入力 - ルートの
VisualElementのBind()に対象のオブジェクトを渡す
以下は選択中のオブジェクトの名前を表示・変更するテキストフィールドを持つエディタウィンドウの実装例です。
using System; using UnityEditor; using UnityEditor.UIElements; using UnityEngine.UIElements; using Object = UnityEngine.Object; namespace BindExample { public sealed class Example : EditorWindow { private TextField _textField; [MenuItem("Window/Example")] public static void ShowWindow() { GetWindow<Example>(); } public void CreateGUI() { _textField = new TextField("Object Name") { // 対象のオブジェクトにシリアライズされているm_Nameとバインドする bindingPath = "m_Name" }; rootVisualElement.Add(_textField); Bind(Selection.activeObject); } private void OnSelectionChange() { Bind(Selection.activeObject); } private void Bind(Object obj) { if (obj != null) { var so = new SerializedObject(obj); rootVisualElement.Bind(so); // バインド // rootVisualElementじゃなくTextFieldに対してバインドしてもOK //_textField.Bind(so); } else { rootVisualElement.Unbind(); // バインド解除 _textField.value = ""; } } } }
これを実行すると下図のように、選択中のオブジェクトの名前がバインディングされてTextFieldに表示されます。
またバインディングは双方向で行われるため、TextFieldの値を変更するとオブジェクトの名前も変わります(変更可能なもののみ)。

SerializedPropertyとバインドする
以下のように、自身でSerializedPropertyを取得してIBindable.BindProperty()に渡すことで、特定のUI要素と直接バインドすることもできます。
using UnityEditor; using UnityEditor.UIElements; using UnityEngine.UIElements; using Object = UnityEngine.Object; namespace BindExample { public sealed class Example : EditorWindow { private TextField _textField; [MenuItem("Window/Example")] public static void ShowWindow() { GetWindow<Example>(); } public void CreateGUI() { _textField = new TextField("Object Name"); // 自分でバインド処理をするのでbindingPathには何も入れない rootVisualElement.Add(_textField); Bind(Selection.activeObject); } private void OnSelectionChange() { Bind(Selection.activeObject); } private void Bind(Object obj) { if (obj != null) { var so = new SerializedObject(obj); // 自身でSerializedPropertyを取得 var nameProp = so.FindProperty("m_Name"); // BindPropertyでTextFieldとバインド _textField.BindProperty(nameProp); } else { rootVisualElement.Unbind(); _textField.value = ""; } } } }
UXMLにバインドパスを指定する
UXMLファイルを使ってレイアウトを組む場合には、バインドするためのパスをUXML側に記述することもできます。
これを行うには以下のように、TextFieldのbinding-pathにパスを入力しておききます。
<UXML xmlns:ui="UnityEngine.UIElements"> <ui:VisualElement> <ui:TextField name="exampleText" label="Name" text="" binding-path="m_Name"/> </ui:VisualElement> </UXML>
エディタウィンドウは以下のように、VisualTreeAssetからレイアウトを生成するように修正しておきます。
using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; namespace BindExample { public sealed class Example : EditorWindow { [SerializeField] private VisualTreeAsset visualTree; public void CreateGUI() { visualTree.CloneTree(rootVisualElement); Bind(Selection.activeObject); } private void OnSelectionChange() { Bind(Selection.activeObject); } [MenuItem("Window/Example")] public static void ShowWindow() { GetWindow<Example>(); } private void Bind(Object obj) { if (obj != null) { var so = new SerializedObject(obj); rootVisualElement.Bind(so); } else { rootVisualElement.Unbind(); var textField = rootVisualElement.Q<TextField>("exampleText"); textField.value = string.Empty; } } } }
あとはこのEditorWindowのスクリプトアセットのInspectorから、先ほど作ったUXMLファイルをアサインすれば完成です。

動作は変わらないので割愛します。