【Unity】【UI Toolkit】Serialized Propertyの値とUIをデータバインディングする方法まとめ

UnityのUI Toolkitで、SeializedPropertyの値とUIをデータバインディングする方法についてまとめました。

Unity 2021.3.16f1

やりたいこと

いま、SerizliedObjectシリアライズされているSerializedPropertyの値をUI ToolkitのUIに表示することを考えます。

従来のIMGUIベースの実装方法では、SerializedObject.FindPropertySerializedPropertyを取得して、自身でUIの表示を更新する必要がありました。

UI ToolkitではVisualElementSerializedPropertyのパスを事前に指定しておくことで、対象の値が変わったときに自動的に表示を更新する仕組みがあります。

本記事ではこのデータバインディングの仕組みについてまとめます。

SerializedObjectとバインドする

SerializedObjectとバインドするには以下を行います。

  1. 対象のUI要素のbindingPathにプロパティのパスを入力
  2. ルートのVisualElementBind()に対象のオブジェクトを渡す

以下は選択中のオブジェクトの名前を表示・変更するテキストフィールドを持つエディタウィンドウの実装例です。

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側に記述することもできます。
これを行うには以下のように、TextFieldbinding-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ファイルをアサインすれば完成です。

UXMLをアサイ

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

参考

docs.unity3d.com

docs.unity3d.com

docs.unity3d.com