はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】Unity2023.2からUI Toolkitの「UxmlElementとUxmlAttribute」を使用することでCustom UI Elementsを実装する際に「UxmlFactoryとUxmlTraits」が必要がなくなった

はじめに

今回はUI ToolkitのUxmlElementUxmlAttributeについて取り上げたいと思います。

docs.unity3d.com
docs.unity3d.com

昔Unity2023.2のTech Stream紹介ブログにて、Custom UI Elementsの実装を簡略化した改善が紹介されていました。

Custom UI Elementsの実装簡素化

古いやり方についてはQualiArtsさんの技術記事を参照すると分かりやすいかと思います。
technote.qualiarts.jp

気づけば今はUnity6 Beta(6000.0)が出ていたので、実際にどうなっているのか試してみたいと思います。

やり方

最初の添付した画像の通り、VisualElementを継承したクラスに[UxmlElement]をつけ、partialをつけます。(SourceGeneratorを利用していて、対応するUxmlSerializedDataがコンパイル時に生成されます)
あとはプロパティとして表示したい値に[UxmlAttribute]をつけます。

using UnityEngine.UIElements;

namespace ExampleUxmlSerializedData
{
    // 以前はUI Builderで使用するためには対応する「UxmlFactory」を継承したクラスを作らなければならなかった
    // 以前はUI Builderでプロパティとして受け取った値の反映は「UxmlTraits」を継承したクラスが必要だった
    // それが[UxmlElement]&[UxmlAttribute]を利用するだけでOKになった!!!
    [UxmlElement]
    public partial class ExampleVisualElement : VisualElement
    {
        // 自動でSerializeされるようになる
        [UxmlAttribute]
        public string MyStringValue { get; set; }
        
        // nameを指定してあげることが可能
        [UxmlAttribute("count")]
        public float Count { get; set; }
        
        public ExampleVisualElement()
        {
            var label = new Label();
            var button = new Button();

            button.clicked += () =>
            {
                label.text = "myStringValue: " + MyStringValue;
                button.text = $"押してね({++Count})";
            };

            // UIに追加された段階で処理を行う(コンストラクタの時点で初期化しても、値が反映されないため)
            RegisterCallback<AttachToPanelEvent>(e =>
            {
                // ここで初期化処理を行う
                label.text = "myStringValue: " + MyStringValue;
                button.text = $"押してね({Count})";
            });
            
            Add(label);
            Add(button);
        }
    }
}

// 生成されたコード
namespace ExampleUxmlSerializedData
{
    public partial class ExampleVisualElement
    {
        [global::System.Runtime.CompilerServices.CompilerGenerated]
        [global::System.Serializable]
        public new class UxmlSerializedData : UnityEngine.UIElements.VisualElement.UxmlSerializedData
        {
            #pragma warning disable 649
            [global::UnityEngine.SerializeField] string MyStringValue;
            [global::UnityEngine.SerializeField, global::UnityEngine.UIElements.UxmlIgnore, global::UnityEngine.HideInInspector] UnityEngine.UIElements.UxmlSerializedData.UxmlAttributeFlags MyStringValue_UxmlAttributeFlags;
            [UnityEngine.UIElements.UxmlAttributeAttribute("count")]
            [global::UnityEngine.SerializeField] float Count;
            [global::UnityEngine.SerializeField, global::UnityEngine.UIElements.UxmlIgnore, global::UnityEngine.HideInInspector] UnityEngine.UIElements.UxmlSerializedData.UxmlAttributeFlags Count_UxmlAttributeFlags;
            #pragma warning restore 649
            
            public override object CreateInstance() => new ExampleVisualElement();
            public override void Deserialize(object obj)
            {
                base.Deserialize(obj);
                var e = (ExampleVisualElement)obj;
                if (ShouldWriteAttributeValue(MyStringValue_UxmlAttributeFlags))
                {
                    e.MyStringValue = this.MyStringValue;
                }
                if (ShouldWriteAttributeValue(Count_UxmlAttributeFlags))
                {
                    e.Count = this.Count;
                }
            }
        }
    }
}

注意点としてはRegisterCallback<AttachToPanelEvent>を利用しないと、コンストラクタ時点ではデシリアライズ処理が走っておらず、値が設定されていないので初期化処理を遅らせる必要があります。

結果

実際にUI Builderを見てみると、ExampleVisualElementLibrary > Project > Custom Controls(C#)の中にあるはずです。

実際に利用している様子

ちゃんとプロパティも表示されていますね。