Version: 2022.2
言語: 日本語
要素管理のベストプラクティス
UXML 要素のリファレンス

UXML ドキュメントをロジックでカプセル化する

プレハブ とは、シーン内で複数回インスタンス化できる、あらかじめ作成されたゲームオブジェクトのことです。プレハブは、再利用可能なコンポーネントの作成に役立ちます。UI Toolkit のビジュアル要素はゲームオブジェクトではないので、プレハブには当てはまりません。ただし、要素の特定の階層をロジックでカプセル化する再利用可能な UI コンポーネントとして カスタムコントロールを作成 することが可能です。UI Toolkit では、UI をゲームやアプリケーションのコードから分離することが推奨されるため、UXML を使用して構造を定義し、USS を使用して見た目を定義し、C# を使用してカスタムコントロールのロジックを定義することができます。

再利用可能な UI コンポーネントの作成

再利用可能な UI コンポーネント - 例えば、以下のような、キャラクターの画像と体力と攻撃力を表示するカードの、CardElement と呼ばれるカスタムコントロール - を作成するとします。

カードのサンプル
カードのサンプル

これは、以下のおおまかな手順で作成できます。

  1. C# で、CardElement という カスタム要素タイプ を宣言します。

  2. UXML で、カスタムコントロールの階層を定義します。2 つのアプローチが使用可能です。どちらのアプローチでも、C# と親 UXML で CardElement をインスタンス化できます。

  3. カスタムコントロールの子要素への参照を見つけます。

  4. プロパティとメソッドを公開し、(C# のクラスの場合と同様に) カスタムコントロールのロジックをカプセル化します。

  5. カスタムコントロールをゲームやアプリケーションのコードに接続します。イベントコールバックを登録してユーザーとのインタラクションを実装することもできます。

UXML 優先のアプローチ

この方法では、カスタム要素 CardElement を階層 UXML ドキュメントに含め、その直下で子要素を宣言し、階層 UXML ドキュメントを テンプレート として使用します。この方法では、階層 UXML ドキュメント内の UI 構造が固定された、よりシンプルなソリューションが提供されます。

以下の C# と UXML の例は、UXML 優先のアプローチを使用して再利用可能な UI を作成する方法を示すものです。

カスタムコントロールクラスの作成

CardElement カスタムコントロールを定義する C# スクリプトを作成します。カスタムコントロールクラスは CardElement に画像とバッジの値を割り当てます。

using UnityEngine;
using UnityEngine.UIElements;

// Define the custom control type.
public class CardElement : VisualElement
{
    // Expose the custom control to UXML and UI Builder.
    public new class UxmlFactory : UxmlFactory<CardElement> {}

    private VisualElement portraitImage => this.Q("image");
    private Label attackBadge => this.Q<Label>("attack-badge");
    private Label healthBadge => this.Q<Label>("health-badge");

    // Use the Init() approach instead of a constructor because 
    // we don't have children yet.
    public void Init(Texture2D image, int health, int attack)
    {
        portraitImage.style.backgroundImage = image;
        attackBadge.text = health;
        healthBadge.text = attack;
    }

    // Custom controls need a default constructor. 
    public CardElement() {}
}

カスタムコントロールの階層の定義

CardElement の階層構造を定義する UXML ドキュメント (CardElement.uxml) を作成します。この例では、USS ファイルを使用して CardElement のスタイルを設定します。

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <Style src="CardElementUI.uss" />
    <CardElement> 
        <ui:VisualElement name="image" />
        <ui:VisualElement name="stats">
            <ui:Label name="attack-badge" class="badge" />
            <ui:Label name="health-badge" class="badge" />
        </ui:VisualElement>
    </CardElement> 
</ui:UXML>

カスタムコントロールのゲームへの接続

カスタムコントロールをゲームに接続するには、以下を行います。

  • CardElement.uxml を親 UXML ドキュメント内でインスタンス化します。UI Builder では、階層 UXML とこの UXML ドキュメントを 行き来する ことができます。
  • MonoBehaviour C# スクリプトから、CardElement を含む CardElement.uxml をインスタンス化します。CardElement をシーンに追加する前に、UQuery を使って CardElement を見つける必要があります。

カスタムコントロールをシーンに追加した後で Init() を呼び出します。

UXML 優先のアプローチのワークフロー
UXML 優先のアプローチのワークフロー

また、要素とのインタラクションを行うためのクリックイベントなど、ゲームプレイに関連するアクションを追加することもできます。

親 UXML の内部でインスタンス化する

以下は、UXML でのインスタンス化の例です。

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:Template name="CardElement" src="CardElement.uxml"/>
    <ui:Instance template="CardElement"/>
    <ui:Instance template="CardElement"/>
    <ui:Instance template="CardElement"/>
</ui:UXML>

ゲームで UXML ドキュメントをレンダリングする方法については、ゲームビューでの UI のレンダリングに関するドキュメント 参照してください。

C# で直接インスタンス化する

ノート: このページ内のサンプルコードは学習目的のため、Resources フォルダー を使用する方法で UXML ファイルを読み込んでいます。ただし、この方法はあまり拡張性がありません。製品版プロジェクトでは 他の方法 で参照を読み込むことをお勧めします。

以下は、C# でのインスタンス化の例です。

using UnityEngine;
using UnityEngine.UIElements;

public class UIManager : MonoBehaviour
{
    public void Start()
    {
        UIDocument document = GetComponent<UIDocument>();

        // Load the UXML document that defines the hierarchy of CardElement.
        // It assumes the UXML file is placed at the "Resources" folder.
        VisualTreeAsset template = Resources.Load<VisualTreeAsset>("CardElement");

        // Create a loop to modify properties and perform interactions 
        // for each card.  It assumes that you have created a function 
        // called `GetCards()` to get all the cards in your game.
        foreach(Card card in GetCards())
        {
            // Instantiate a template container.
            var templateContainer = template.Instantiate();

            // Find the custom element inside the template container.
            var cardElement = templateContainer.Q<CardElement>();

            // Add the custom element into the scene.
            document.rootVisualElement.Add(cardElement);

            // Initialize the card.
            cardElement.Init(card.image, card.health, card.attack);

            // Register an event callback for additional interaction.
            cardElement.RegisterCallback<ClickEvent>(SomeInteraction);
        }
    }

    private void SomeInteraction(ClickEvent evt)
    {
        // Interact with the elements here.
    }
}

要素優先のアプローチ

このアプローチでは、階層 UXML ドキュメントに子要素のみを含め、C# を使用して CardElement クラス定義に 階層 UXML ドキュメントをロード します。このアプローチでは、カスタムコントロールに柔軟な UI 構造が提供されます。例えば、特定の条件に応じて異なる階層 UXML ドキュメントをロードできます。

以下の C# と UXML の例は、要素優先のアプローチを使用して再利用可能な UI を作成する方法を示すものです。

カスタムコントロールクラスの作成

CardElement カスタムコントロールを定義する C# スクリプトを作成します。CardElement に画像とバッジの値を割り当てるコンストラクターを定義 するのに加え、このカスタムコントロールは、クラス定義内に階層 UXML ドキュメントをロードします。

using UnityEngine;
using UnityEngine.UIElements;

// Define the custom control type.
public class CardElement : VisualElement
{
    // Expose the custom control to UXML and UI Builder.
    public new class UxmlFactory : UxmlFactory<CardElementA> {}

    private VisualElement portraitImage => this.Q("image");
    private Label attackBadge => this.Q<Label>("attack-badge");
    private Label healthBadge => this.Q<Label>("health-badge");


    // Custom controls need a default constructor. This default constructor 
    // calls the other constructor in this class.
    public CardElement() {}

    // Define a constructor that loads the UXML document that defines 
    // the hierarchy of CardElement and assigns an image and badge values.
    public CardElement(Texture2D image, int health, int attack)
    {
        // It assumes the UXML file is called "CardElement.uxml" and 
        // is placed at the "Resources" folder.
        var asset = Resources.Load<VisualTreeAsset>("CardElement");
        asset.CloneTree(this);

        portraitImage.style.backgroundImage = image;
        attackBadge.text = health.ToString();
        healthBadge.text = attack.ToString();
    }
}

ノート: パフォーマンスに関して懸念がある場合は、遅延初期化を使用して、フィールドで参照をキャッシュし、クエリの再評価を頻繁に行いすぎないようにします。

カスタムコントロールの階層の定義

CardElement の子要素の階層を定義する UXML 文書 (CardElement.uxml) を作成します。この例では、USS ファイルで CardElement のスタイルを設定しています。

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <Style src="CardElementUI.uss" /> 
    <ui:VisualElement name="image" />
    <ui:VisualElement name="stats">
        <ui:Label name="attack-badge" class="badge" />
        <ui:Label name="health-badge" class="badge" />
    </ui:VisualElement>
</ui:UXML>

カスタムコントロールのゲームへの接続

以下を行うことで、カスタムコントロールをゲームに接続できます。

  • 親 UXML ドキュメント内の CardElement.uxml をインスタンス化します。UI Builder では、子要素が C# からロードされるため、階層 UXML とこの UXML ドキュメントを行き来することはできません。
  • MonoBehaviour C# スクリプトから、CardElement を格納する CardElement.uxml をインスタンス化します。

カスタムコントロールをシーンに追加する前にコンストラクターを呼び出します。

要素優先のアプローチのワークフロー
要素優先のアプローチのワークフロー

また、要素とのインタラクションを行うためのクリックイベントなど、ゲームプレイに関連するアクションを追加することもできます。

親 UXML の内部でインスタンス化する

以下は、UXML でのインスタンス化の例です。

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
   <CardElement />
   <CardElement />
   <CardElement />
</ui:UXML>

ゲームで UXML ドキュメントをレンダリングする方法については、ゲームビューでの UI のレンダリングに関するドキュメント 参照してください。

C# で直接インスタンス化する

以下は、C# でのインスタンス化の例です。

using UnityEngine;
using UnityEngine.UIElements;

public class UIManager : MonoBehaviour
{
    public void Start()
    {
        UIDocument document = GetComponent<UIDocument>();

        // Create a loop to modify properties and perform interactions 
        // for each card. It assumes that you have created a function 
        // called `GetCards()` to get all the cards in your game.
        foreach(Card card in GetCards())
        {
            var cardElement = new CardElement(card.image, card.health, card.attack);

            // Register an event callback for additional interaction.
            cardElement.RegisterCallback<ClickEvent>(SomeInteraction);

            // Add the custom element into the scene.
            document.rootVisualElement.Add(cardElement);
        }
    }

    private void SomeInteraction(ClickEvent evt)
    {
        // Interact with the elements here.
    }
}

より複雑な要素の構築

プロジェクトの UI が複雑になる場合は、ロジックをより高いレベルで分離することが推奨されます。そうすることで、ゲームやアプリケーションの他の部分の UI 構成が行いやすくなります。

このページで紹介されている概念を応用すれば、小さな汎用的なコンポーネントから、徐々に、より特化したコンポーネントを作成できます。例えば、メインのタイトル画面を作成し、そこからユーザーがオプションメニューと About セクションにアクセスできるようにするには、3 つの異なる子 UXML ドキュメントを持つ TitleScreenManager 要素を作成します。それぞれの子が独自の要素 (Title、Options、About) を定義するようにします。

メインタイトル画面の例 詳細は こちら の YouTube 動画を参照してください。

その他の参考資料

要素管理のベストプラクティス
UXML 要素のリファレンス