なつねこメモ

主にプログラミング関連のメモ帳 ♪(✿╹ヮ╹)ノ 書いてあるコードは自己責任でご自由にどうぞ。記事本文の無断転載は禁止です。

Unity の UXML で独自のコントロールを作成したい

前回はコールバックを作成しましたが、今回は独自のコントロールを作ってみましょう!
ということで、早速 UXML と C# スクリプトを書きます。

たとえば、こんな感じのメッセージボックスを作りたい場合は、

以下のような UXML になると思います。

<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:engine="UnityEngine.UIElements"
  xmlns:editor="UnityEditor.UIElements"
  xsi:noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd"
>
  <engine:VisualElement class="flex flex-row border-2 border-slate-400 rounded-md p-2 my-2 items-center">
    <engine:VisualElement class="flex justify-center items-center">
      <engine:Image name="icon" class="w-10 h-10 min-h-full" />
    </engine:VisualElement>
    <engine:VisualElement class="flex-grow ml-2">
      <engine:TextElement name="text"  class="text-lg" />
    </engine:VisualElement>
  </engine:VisualElement>
</engine:UXML>

そして、これの icontext 部分を、親コンポーネントから受け取る値だったとします。
その場合、次のような C# スクリプトを記述します。

// ------------------------------------------------------------------------------------------
//  Copyright (c) Natsuneko. All rights reserved.
//  Licensed under the MIT License. See LICENSE in the project root for license information.
// ------------------------------------------------------------------------------------------

using System.Collections.Generic;

using NatsunekoLaboratory.VRAvatarToolkit.Enums;
using NatsunekoLaboratory.VRAvatarToolkit.Extensions;

using UnityEditor;

using UnityEngine;
using UnityEngine.UIElements;

namespace NatsunekoLaboratory.VRAvatarToolkit.Controls
{
    public class MessageBox : VisualElement
    {
        private readonly Image _icon;

        private readonly TextElement _text;

        public MessageBoxIcon Icon
        {
            set => _icon.style.backgroundImage = new StyleBackground(Background.FromTexture2D(EditorGUIUtility.Load(value.ToResourceString()) as Texture2D));
        }

        public string Text
        {
            get => _text.text;
            set => _text.text = value;
        }

        public MessageBox()
        {
           // UXML / USS 読み込み部分は省略
            _icon = this.Q<Image>("icon");
            _text = this.Q<TextElement>("text");
        }

        public new class UxmlFactory : UxmlFactory<MessageBox, UxmlTraits> { }

        public new class UxmlTraits : VisualElement.UxmlTraits
        {
            private readonly UxmlEnumAttributeDescription<MessageBoxIcon> _icon = new UxmlEnumAttributeDescription<MessageBoxIcon> { name = "icon", defaultValue = MessageBoxIcon.Info };
            private readonly UxmlStringAttributeDescription _text = new UxmlStringAttributeDescription { name = "text", defaultValue = "" };

            public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
            {
                get { yield break; }
            }

            public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
            {
                base.Init(ve, bag, cc);

                ((MessageBox)ve).Text = _text.GetValueFromBag(bag, cc);
                ((MessageBox)ve).Icon = _icon.GetValueFromBag(bag, cc);
            }
        }
    }
}

該当コントロールのクラス実装のインナークラスとして、 UxmlTraits みたいな感じで作ってあげると、バインディングされた値を受け取ることが出来ます。
最後に、 Init メソッド経由で受け取れば、初期値がバインドされます。

2回目以降は普通に C# スクリプトからプロパティ経由でアクセス可能なので、特に実装は必要ありません。
ということで、今日のメモでした!