【Unity】【UIToolkit】UIBuilderでランタイムUIをサクッと組み立てる

UnityのUIToolkitのランタイムUIを、UIBuilderを使って組み立てる方法について簡単にまとめました。

Unity 2022.1.1f1

はじめに

Unity2021.2から、UI Toolkit(旧UI Elements)でランタイムのUIを作成できるようになりました。
また、UI ToolkitにおけるUIレイアウト用ツールであるUI BuilderもUnityに組み込まれました。

本記事ではUI Builderを使って以下のようなランタイムのUIを組み立てる方法を簡潔にまとめます。

UIを組み立てる

なおUI Toolkit(UI Elements)の基礎知識については前提知識とします。
UXMLなどの概念がわからない方は以下の記事にまとめていますので、必要に応じて参照してください。

light11.hatenadiary.com

ビューを作る

それではまずビューを作っていきます。

最初に、Create > UI Toolkit > Panel Settings Asset から Panel Settings を作成します。
このアセット自体は後ほど使用しますが、これと一緒に自動生成される UnityDefaultRuntimeTheme.tss が必要なので先に生成しておきます。
(マニュアルには UnityDefaultRuntimeTheme が最初から存在する前提で書かれているので、いずれ変更入るかも?

次に Window > UI Toolkit > UI Builder から UI Builder を開きます。
ウィンドウが開いたら以下の操作を行い、新しいUXMLを作成します。

  1. File > New から新しいファイルを作成
  2. Theme を Unity Default Runtime Theme に変更
  3. Hierarchy から uxml を選択
  4. Inspector > Canvas Size > Match Game View にチェックを入れる

Match Game View

次に ListView を Hierarchy にドラッグ & ドロップします。
ListView が作成されたら右クリックして FooList という名前にリネームしておきます。
また、Inspector からサイズ、背景色、枠線を適当に編集しておきます。

背景色と枠線

ここまでできたら Ctrl + S を押下して MainView という名前で任意の場所に保存しておきます。

次に同様にしてリストの要素となるボタンを配置した UXML を作成します。

リスト要素

これは ListElement という名前で任意の場所に保存しておきます。

ビューを制御するスクリプトを書く

次にビューを制御するためのスクリプトを書きます。
まず、ListElement 内の FooButton を制御するためのスクリプトを記述します。

using UnityEngine;
using UnityEngine.UIElements;

public sealed class FooButtonController
{
    private readonly Button _button;

    public FooButtonController(VisualElement visualElement)
    {
        // VisualElementからButtonを取得する
        _button = visualElement.Q<Button>("FooButton");

        _button.clicked += OnClick;
    }

    /// <summary>
    ///     ボタンの文字列を設定する。
    /// </summary>
    /// <param name="text"></param>
    public void SetText(string text)
    {
        _button.text = text;
    }

    private void OnClick()
    {
        // クリックされたログを出力する
        Debug.Log($"Clicked: {_button.text}");
    }
}

詳細はコメントに記述したとおりです。

次に、MainView 内の FooList を制御するためのスクリプトを書きます。

using System.Collections.Generic;
using UnityEngine.UIElements;

public sealed class FooListController
{
    private IReadOnlyList<string> _buttonTexts;
    private readonly VisualTreeAsset _elementTemplate;
    private readonly ListView _fooList;

    public FooListController(VisualElement root, VisualTreeAsset elementTemplate, List<string> buttonTexts)
    {
        _fooList = root.Q<ListView>("FooList");
        _elementTemplate = elementTemplate;

        _fooList.makeItem = () =>
        {
            // リスト要素をインスタンス化して返す
            var element = _elementTemplate.Instantiate();
            var buttonController = new FooButtonController(element);
            element.userData = buttonController; // ControllerはuserDataという汎用データ受け渡し用フィールドに格納しておく

            return element;
        };

        _fooList.bindItem = (item, index) =>
        {
            // リスト要素にデータを設定する
            var controller = (FooButtonController)item.userData;
            controller.SetText(buttonTexts[index]);
        };

        _fooList.fixedItemHeight = 120;
        _fooList.itemsSource = buttonTexts;
    }
}

最後にこれらのエントリポイントとなるスクリプトを作成します。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public sealed class MainView : MonoBehaviour
{
    [SerializeField] private VisualTreeAsset _elementTemplate;

    private void OnEnable()
    {
        // UIDocumentコンポーネントについては次節で説明
        var uiDocument = GetComponent<UIDocument>();

        var buttonTexts = new List<string>
        {
            "First",
            "Second",
            "Third"
        };

        new FooListController(uiDocument.rootVisualElement, _elementTemplate, buttonTexts);
    }
}

再生する

最後にシーンをセットアップして再生します。

UI Toolkit で作った UI をランタイムで表示するには、まず GameObject に UI Document コンポーネントをアタッチします。
このコンポーネントの Panel Settings に冒頭で作成した PanelSettings アセットをアサインします。
また、Source Asset に表示する UXML(今回はMainView)をアサインします。

UI Document

次に、生成された UI を制御するために同じ GameObject に MainView コンポーネントをアタッチします。
Element Template に ListElement アセットをアサインしておきます。

Main View

あとは Unity を再生すれば、UI が表示されることが確認できます。

結果

参考

docs.unity3d.com