プレハブは、シーン内で複数回インスタンス化できる、あらかじめ用意されたゲームオブジェクトです。プレハブは再利用可能なコンポーネントを作成するのに便利です。UI Toolkit のビジュアル要素はゲームオブジェクトではないため、プレハブは適用されません。ただし、ロジックを使用して要素の特定の階層をカプセル化する再利用可能な UI コンポーネントとしてカスタムコントロールを作成できます。UI Toolkit では、UI をゲームコードやアプリケーションコードから分離することが推奨されているため、UXML を使用した構造体の定義、USS を使用した外観の定義、さらに C# を使用したカスタムコントロールのロジックの定義を行うことができます。
例として、カードゲームを作成したいとします。ライフや攻撃など、異なる統計情報を持つカードを表示する必要があります。
CardElement というカスタムコントロールを作成し、キャラクターの画像、ライフ、攻撃に関する統計を表示します。このカスタムコントロールをゲーム内の各カードで再利用できます。
以下に示すのは、これを行うための一般的なステップです。
C# で CardElement というカスタム要素型を宣言します。
UXML で、カスタムコントロールの階層を定義します。2 つのアプローチを使用できます。どちらのアプローチも、C# および親 UXML での CardElement のインスタンス化をサポートします。
カスタムコントロールの子要素への参照を検索します。
C# クラスで行う場合と同じ方法で、プロパティとメソッドを公開し、カスタムコントロールでロジックをカプセル化します。
カスタムコントロールをゲームコードまたはアプリケーションコードに接続します。イベントコールバックを登録してユーザーインタラクションを実装することもできます。
このアプローチでは、階層 UXML ドキュメントにカスタム要素 CardElement を加え、その直下で子要素を宣言し、階層 UXML ドキュメントをテンプレートとして使用します。このアプローチは、階層 UXML ドキュメント内に固定の UI 構造体を持つ、より単純なソリューションを実現します。
以下の C# と UXML の例は、UXML 優先のアプローチを使用して再利用可能な UI を作成する方法を示しています。
CardElement カスタムコントロールを定義する C# スクリプトを作成します。カスタムコントロールクラスは、CardElement に画像とバッジの値を割り当てます。
using UnityEngine;
using UnityEngine.UIElements;
// Define the custom control type.
[UxmlElement]
public partial class CardElement : VisualElement
{
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 をインスタンス化します。UI Builder では、階層 UXML とこの UXML ドキュメント間を前後に移動することができます。CardElement を含む CardElement.uxml をインスタンス化します。シーンに追加する前に、UQuery を使用して CardElement を見つける必要があります。カスタムコントロールをシーンに追加した後に Init() を呼び出します。
要素を操作するためのクリックイベントなど、ゲームプレイ関連のアクションを追加することもできます。
親 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.
[UxmlElement]
public partial class CardElement : VisualElement
{
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>
以下の手順で、カスタムコントロールをゲームに接続できます。
CardElement.uxml をインスタンス化します。UI Builder では、階層 UXML とこの UXML ドキュメント間を前後に移動することはできません。これは、子要素が 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 の調整が容易になります。
このページの概念を適用して、より小さく汎用なコンポーネントから徐々に特殊なコンポーネントをビルドできます。例えば、ユーザーがオプションメニューとバージョン情報セクションにアクセスできるメインタイトル画面をビルドするには、3 つの異なる子 UXML ドキュメントを持つ TitleScreenManager 要素を作成します。それぞれが独自の要素を定義します。Title (タイトル)、Options (オプション)、About (バージョン情報)。