Version: 2021.3
言語: 日本語
カスタムインスペクターの作成
ビジュアルツリー

ランタイム UI の作成

このページでは、UI Toolkit を使用して簡単なキャラクター選択画面を設定する手順を説明します。UI エレメントとテンプレートの作成、シーンの設定、スクリプトロジックを UI に適用する方法について説明します。このガイドでは、USS によるスタイリングはカバーせず、デフォルトのスタイルとテーマのみを使用します。

このガイドの最終的なソースコードは、このページの 下の方 にあります。

説明項目 UI Builder、ListViewLabelPanelSettingsUIDocument、選択処理

このガイドでは、以下の手順で説明しています。

  • メイン UI ビューの作成
  • シーンの設定
  • 表示するサンプルデータの作成
  • メインビュー用のコントローラの作成
  • リストエントリーの UI テンプレート作成
  • リストエントリーのコントローラー作成
  • ユーザーの選択への反応

メイン UI ビューの作成

最終的な UI 画面は、2 つの個別の UI テンプレート (UXML) で構成されています。メインビューテンプレートには、キャラクター名の入ったリスト、選択したキャラクターの詳細を表示するための小さなパネル、ボタンが含まれています。このセクションでは、UI Builder を使用してこのテンプレートを設定します。

ノート: UI Builder を使い慣れていて、このステップを飛ばしたい場合は、このページの下にある MainView の UXML コードをコピーして、新しいファイルに直接貼り付けることができます。Assets/UI/MainView.uxml という名前で保存してください。

メインビューの UI レイアウト設定
メインビューの UI レイアウト設定

メニュー Window > UI Toolkit > UI Builder から、UI Builder ウィンドウを開きます。ビューポートの左上にあるファイルメニューを使用して、新しい UXML ドキュメントを作成します。

UI Builder のファイルメニュー
UI Builder のファイルメニュー

ゲームの UI を開発するときは、UI Builder のビューポートの右上にある Unity Default Runtime Theme を常に選択します。エディターとランタイムのテーマではデフォルトのフォントサイズと色が異なるため、レイアウトに影響が出ます。

ライブラリからドラッグして新しい要素を作成
ライブラリからドラッグして新しい要素を作成

Hierarchy で新しい UXML ファイルを選択し、Match Game View のチェックボックスを有効にします。Unity エディターを横向きの解像度に設定していない場合は、設定が必要なことがあります。

Match Game View を有効にする
Match Game View を有効にする

いよいよ UI 要素を作成します。 Library から Hierarchy にドラッグして、新しい VisualElement を作成します。

Library からドラッグして新しい要素を作成
Library からドラッグして新しい要素を作成

新しい要素は、画面全体を覆う必要があるため、flex-grow プロパティを 1 に設定する必要があります。Hierarchy から要素を選択し、右側の Inspector で Flex と書かれた折りたたみ部分を探します。Grow の値を 0 から 1 に変更します。

Flex プロパティの設定
Flex プロパティの設定

この VisualElement のすべての子要素を画面の中央に配置するには、VisualElement の Align プロパティを変更します。 Align ItemsJustify Content の両方を、center (中央) に設定する必要があります。

子のセンタリング
子のセンタリング

最後に、Background > Color で背景色を選択します。このステップは必須ではありません。この例では、#732526 の色を使用しています。

ルート要素の背景色
ルート要素の背景色

次に、既存の VisualElement の下に新しい VisualElement を作成します。これは、UI の左側と右側のセクションの親コンテナになります。

子の VisualElement を加える
子の VisualElement を加える

この新しい要素の flex-direction プロパティを row (行) に設定します (デフォルトでは列になっています)。また、高さを 350 ピクセルに固定する必要があります。

中央コンテナのプロパティ
中央コンテナのプロパティ

現在の UI はこのように表示されるはずです。ゲームビュー の解像度やアスペクト比によって、画面表示が異なる場合があることに注意してください。

内部に空の要素を持つ背景コンテナ
内部に空の要素を持つ背景コンテナ

キャラクター名のリストを作成するには、ListView コントロールを Library から選択し、先ほど作成した VisualElement の下に子要素として加えます。この要素を選択し、Inspector で CharacterList という名前を割り当てます。これは、後でコントローラースクリプトからこのリストにアクセスするために必要です。

内部に空の要素を持つ背景コンテナ
内部に空の要素を持つ背景コンテナ

リストの幅を 230 ピクセルに固定します。また、これから作成する次の要素までの距離を確保するために、右側に 6 ピクセルの幅のマージンを与えます。

キャラクターリストの Size と Margin の折りたたみ部分
キャラクターリストの Size と Margin の折りたたみ部分

また、リストの背景色を割り当て、丸みを帯びた境界線を設定することができます。このガイドでは、背景に #6E3925 を、境界線の色に #311A11 を使用し、境界線の幅を 4px、半径を 15px に設定しています。このステップは必須ではありません。

スタイルを設定したキャラクターリスト
スタイルを設定したキャラクターリスト

CharacterList と同じ親に、新しい VisualElement を追加します。これには、キャラクター詳細パネルとボタンが含まれます。 Align 折りたたみの下で、Align Items の設定を flex-end に、Justify Content の設定を space-between に変更します。

コンテンツプロパティの調整
コンテンツプロパティの調整

この新しいコンテナに、新しい VisualElement を加えます。これがキャラクター詳細パネルになります。ユーザーが左のリストからキャラクターを選択すると、そのキャラクターの画像、名前、クラスが表示されます。

要素に固定幅 276 ピクセルを設定し、Align ItemsJustify Contentcenter (中央) に切り替えます。また、要素に 8 ピクセル幅のパディングを加えます。これにより、子要素はコンテナの境界から最小限の距離を保つことができます。

キャラクター詳細コンテナのプロパティ
キャラクター詳細コンテナのプロパティ

背景色を #AA5939 に、境界色を #311A11 に、境界線幅 4 ピクセル、半径 15 ピクセルでパネルのスタイルを設定します。このステップは必須ではありません。

これで、UI レイアウトは以下の画像のようになります。

空っぽのキャラクター詳細パネル
空っぽのキャラクター詳細パネル

次に、キャラクターの詳細に、個々の UI コントロールを加えます。まず、キャラクターの画像です。これは、背景のフレームと前景の画像の 2 つの要素で構成されています。

まず、背景フレーム用のキャラクター詳細コンテナに、新しい VisualElement を加えます。固定サイズ 120x120 ピクセル、パディング 4 ピクセルを割り当て、含まれる画像が境界に直接触れないようにします。

幅 2 ピクセル、半径 15 ピクセルのボーダーで、色は #311A11、背景色は #FF8554 で要素のスタイルを設定します。代わりに、独自の色やスタイルを自由に適用することができます。

キャラクター画像用背景フレーム
キャラクター画像用背景フレーム

実際の画像は、先ほど作成したフレームの子として、新しい VisualElement を加えます。名前は CharacterPortrait とし、後でコントローラースクリプトの中でアクセスできるようにします。

Flex > Grow を 1 に設定します。そうすれば、画像は利用可能なスペースをすべて利用します。また、「 Background > Scale Mode 」のスケーリングモードを「 scale-to-fit 」に変更することを確認します。これにより、正しいアスペクト比を維持したまま、要素のサイズに合わせて画像を拡大または縮小することができます。

ポートレート画像用 VisualElement
ポートレート画像用 VisualElement

次に、選択したキャラクターの名前とクラスを表示するために後で使用する 2 つのラベルコントロールをキャラクター詳細コンテナに加えます。これらを CharacterNameCharacterClass と名付けます。

名前とクラスのラベルを加えます
名前とクラスのラベルを加えます

クラスよりもキャラクターの名前を目立たせるには、そのラベルのフォントサイズを 18 に変更し、スタイルを B (太字) に設定します。

フォントの設定を変更する
フォントの設定を変更する

UI 画面は下図のように表示されます。

完成したキャラクター詳細パネル
完成したキャラクター詳細パネル

最後に、右側の UI コンテナに Button コントロールを追加します。後にこのボタンにコントローラースクリプトでアクセスし、キャラクターが選択されたときや選択解除されたときに有効または無効にします。ボタンの名前は SelectCharButton とし、固定幅を 150 ピクセルにします。また、ボタンのラベルテキストを Select Character に設定します。

キャラクター選択用ボタン追加
キャラクター選択用ボタン追加

ボタンのスタイルは、背景色を #FF8554 に、境界色を #311A11 に、境界線幅 2 ピクセルに設定します。このステップは必須ではありません。

完成したメインビューは、以下の画像のように表示されます。

最終的なメインビューのレイアウト
最終的なメインビューのレイアウト

UXML テンプレートを Assets/UI/MainView.uxml という名前で保存します。 また、このテンプレートの最終的な UXML コード は、ページの下の方に記載されています。

シーンの設定

このセクションでは、前セクションで作成した UI テンプレートをランタイムにゲームにロードして表示する方法について学びます。

まず始めに、PanelSettings アセットを作成する必要があります。このアセットで、スケーリングモードやレンダリング順序など、画面の設定を定義します。また、UI Toolkit Debugger に表示される UI の名前もこのアセットで決定されます。

パネル設定アセットを新規に作成する
パネル設定アセットを新規に作成する

Project ビューで右クリックし、新しい Panel Settings Asset を作成します。Create > UI Toolkit > Panel Settings Asset を選択します。新しく作成したファイルに GameUI_Panel という名前を付けます。 このガイドでは、すべての設定をデフォルト値のままにしておくことができます。

デフォルトの PanelSettings を変更する必要はありません。
デフォルトの PanelSettings を変更する必要はありません。

前セクションのメインビュー UI テンプレートを表示するには、シーン内に新しいゲームオブジェクトを作成する必要があります。それに、UIDocument コンポーネントをアタッチします。

Unity が再生モードに入ると、UIDocument は割り当てられた VisualTreeAsset を自動的にロードします。VisualTreeAsset は、UXML テンプレートです。MainView.uxml と、新しい GameUI_Panel パネル設定の両方をコンポーネントに割り当てます。

UI Document コンポーネント
UI Document コンポーネント

ノート: UI Document コンポーネントに PanelSettings アセットを割り当てない場合は、自動的にプロジェクトを検索し、最初に見つかった Panel Settings アセットを自動的に使用します。アセット名を変更したり移動したりする場合は、この点に注意してください。

Unity エディターで再生モードに入り、ゲームビューに表示される UI を確認できるようになりました。

ランタイムに表示される UI
ランタイムに表示される UI

ノート: シーン内に複数の UI Document がある場合、同じパネル設定アセットをすべてに割り当てることができます。これにより、すべての UI が同じパネルに描画されるようになり、パフォーマンスが最適化されます。

表示するサンプルデータの作成

このセクションでは、UI のキャラクターリストにデータを入力するためのサンプルデータを作成します。

キャラクターリストには、キャラクター名、クラス、ポートレート画像を保持するシンプルなクラスが必要です。 新しい ScriptableObject スクリプト Assets/Scripts/CharacterData.cs を作成し、次のコードをファイルに貼り付けてください。

using UnityEngine;

public enum ECharacterClass
{
    Knight, Ranger, Wizard
}

[CreateAssetMenu]
public class CharacterData : ScriptableObject
{
    public string m_CharacterName;
    public ECharacterClass m_Class;
    public Sprite m_PortraitImage;
}

[CreateAssetMenu] 属性は、Create メニューに自動的にエントリーを加えます。 Project ビューでフォルダーを右クリックし、新しい ScriptableObject のインスタンスを作成します。

新規作成メニューのエントリー
新規作成メニューのエントリー

ここで、いくつかの CharacterData インスタンスを作成し、ランダムなデータで埋める必要があります。これらのインスタンスをすべて Resources/Characters というフォルダーに配置します。後で、このフォルダーからすべてのキャラクターデータを自動的に解析してロードするスクリプトを作成します。

サンプルキャラクターをいくつか作成する
サンプルキャラクターをいくつか作成する

リストエントリーの UI テンプレート作成

このセクションでは、リスト内の個々のエントリー用の UI テンプレートを作成します。ランタイムに、コントローラースクリプトは各キャラクターに対してこの UI のインスタンスを作成し、リストに追加します。 キャラクターリストのエントリーの UI は、色のついた背景フレームとキャラクター名で構成されています。

キャラクター名を表示するリストエントリー
キャラクター名を表示するリストエントリー

ノート: このステップを飛ばしたい場合は、このページの下にある list entry の UXML コードをコピーして、新しいファイルに直接貼り付けることができます。Assets/UI/ListEntry.uxml という名前で保存してください。

メニュー Window > UI Toolkit > UI Builder から、UI Builder ウィンドウを開きます。 File > New を選択して、新しい UXML テンプレートを作成します。

UI Builder で新しい UXML テンプレートを作成
UI Builder で新しい UXML テンプレートを作成

背景に VisualElement を追加し、高さを 41 px に固定します。エントリー内のテキストは左寄せにして要素の中央に配置する必要があるので、Align 折りたたみ部分を開き、Align Items から Left (左)、 Justify Contentcenter (中央) に設定します。 また、左パディングを 10 px に設定し、ラベルがフレームの左境界線から最小限の距離を保つようにします。

背景色を #AA5939、境界線幅 2 ピクセル、半径 15 ピクセル、境界色を #311A11 で、パネルのスタイルを設定します。このステップは必須ではありません。

背景の VisualElement
背景の VisualElement

既存の VisualElement の子としてラベルを追加し、その名前を CharacterName とします。これは、後でコントローラースクリプトでアクセスできるようにするためです。Font StyleB (太字) に、フォントサイズを 18 に設定します。

キャラクター名のラベルを追加
キャラクター名のラベルを追加

UXML テンプレートを Assets/UI/ListEntry.uxml という名前で保存します。 また、このテンプレートの最終的な UXML コード は、ページの下の方に記載されています。

リストエントリーのコントローラー作成

このセクションでは、リストエントリーのコントローラースクリプトを作成します。このスクリプトの目的は、リストエントリーの UI にキャラクターインスタンスのデータを表示することです。キャラクター名のラベルにアクセスし、指定したキャラクターインスタンスの名前を表示するように設定する必要があります。

新規スクリプト Assets/Scripts/UI/CharacterListEntryController.cs を作成し、以下のコードを貼り付けます。

using UnityEngine.UIElements;

public class CharacterListEntryController
{
    Label m_NameLabel;

    public void SetVisualElement(VisualElement visualElement)
    {
        m_NameLabel = visualElement.Q<Label>("CharacterName");
    }

    public void SetCharacterData(CharacterData characterData)
    {
        m_NameLabel.text = characterData.m_CharacterName;
    }
}

このクラスには 2 つの関数があり、どちらも Set 関数です。

SetVisualElement(VisualElement visualElement) この関数は、前のセクションで作成した ListEntry UI テンプレートのインスタンスであるビジュアル要素を受け取ります。メインビューコントローラーがこのインスタンスを作成します。この関数の目的は、UI 要素内のキャラクター名ラベルへの参照を取得することです。

SetCharacterData(CharacterData characterData) この関数は、このリスト要素が表示する名前を持つキャラクターを取得します。ListView の要素リストはプールされて再利用されるため、どのキャラクターのデータを表示するかを変更するために Set 関数が必要です。

CharacterListEntry クラスは、MonoBehaviour ではないことに注意してください。UI Toolkit のビジュアル要素はゲームオブジェクトではないので、コンポーネントをアタッチすることはできません。代わりに、このクラスは、次のセクションで、userData プロパティにアタッチされます。

メインビューのコントローラーの作成

ここでは、メインビューのキャラクターリスト用のコントローラースクリプトと、それをインスタンス化してビジュアルツリーに割り当てる MonoBehaviour スクリプトを作成します。

まず、Assets/Scripts/UI/CharacterListController.cs の下に新しいスクリプトを作成し、次のコードを貼り付けます。

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

public class CharacterListController
{
    public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
    {
    }
}

InitializeCharacterList() メソッドを後で入力しますが、今は空のメソッドを追加しておくことが重要です。次のセクションでそれを呼び出すことができるようにするためです。

コントローラースクリプトをメインビューにアタッチ

CharacterListEntryController と同様に、 CharacterListControllerMonoBehaviour ではないので、別の方法でビジュアルツリーにアタッチする必要があります。 UIDocument と同じゲームオブジェクトにアタッチできる MonoBehaviour スクリプトを作成する必要があります。これにより、CharacterListController をインスタンス化し、ビジュアルツリーにアタッチします。

新規スクリプト Assets/Scripts/UI/MainView.cs を作成し、以下のコードを貼り付けます。

using UnityEngine;
using UnityEngine.UIElements;

public class MainView : MonoBehaviour
{
    [SerializeField]
    VisualTreeAsset m_ListEntryTemplate;

    void OnEnable()
    {
        // UXML は、すでに UIDocument component コンポーネントによってインスタンス化済み
        var uiDocument = GetComponent<UIDocument>();

        // キャラクターリストコントローラーを初期化
        var characterListController = new CharacterListController();
        characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
    }
}

Unity エディターで、UIDocument があるのと同じゲームオブジェクトに、このスクリプトをアタッチします。ListEntry.uxmlList Entry Template プロパティに割り当てます。

メインビュースクリプトを加え、参照を割り当てる
メインビュースクリプトを加え、参照を割り当てる

MainView UXML のインスタンス化は、同じゲームオブジェクトの UIDocument コンポーネントで自動的に行われるため、スクリプトコンポーネントで行う必要はありません。MainView スクリプトは UIDocument コンポーネントにアクセスし、すでにインスタンス化されているビジュアルツリーの参照を取得します。次に、CharacterListController のインスタンスを作成し、ビジュアルツリーのルート要素と、個々のリスト要素に使用される UXML テンプレートを渡します。

ノート: UI が再ロードされると、UIDocument コンポーネントを含む同じゲームオブジェクト上の関連する MonoBehaviour コンポーネントが再ロード前に無効になり、再ロード後に再び有効になります。したがって、これらの MonoBehaviour の OnEnableOnDisable メソッドに UI と相互作用するコードを配置するのが、効率良い方法です。

すべてのキャラクターデータインスタンスを列挙

コントローラースクリプトに最初に加える機能は、先に作成したキャラクターデータのインスタンスをすべて列挙する関数です。これらは、リストを埋めるために使用されます。

以下のコードを CharacterListController クラスにコピーしてください。

List<CharacterData> m_AllCharacters;

void EnumerateAllCharacters()
{
    m_AllCharacters = new List<CharacterData>();
    m_AllCharacters.AddRange(Resources.LoadAll<CharacterData>("Characters"));
}

ノート: このコードは、キャラクターインスタンスを Resources/Characters フォルダーに作成したと想定しています。別のフォルダーにキャラクターを配置した場合は、フォルダー名を適宜調整する必要があります。

ここで、初期化中に EnumerateAllCharacter メソッドを呼び出す必要があります。 InitializeCharacterList メソッドの先頭にその呼び出しを加えてください。

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
    EnumerateAllCharacters();
}

UI 要素への参照を取得

このセクションでは、InitializeCharacterList メソッドのコンテンツを記入します。最初に行う必要があるのは、情報を表示するためにアクセスが必要なすべての UI コントロールへの参照を個々に取得することです。名前、USS クラス、型、またはこれらの組み合わせによって個々の UI コントロールを取得するには API の UQuery ファミリーを使用します。

CharacterListController クラス内のコードを以下のコードで拡張します。

// リストエントリー用 UXML テンプレート
VisualTreeAsset m_ListEntryTemplate;

// UI 要素参照
ListView m_CharacterList;
Label m_CharClassLabel;
Label m_CharNameLabel;
VisualElement m_CharPortrait;
Button m_SelectCharButton;

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
    EnumerateAllCharacters();

     // リストエントリーのテンプレートへの参照を保存
    m_ListEntryTemplate = listElementTemplate;

    // キャラクターリスト要素への参照を保存
    m_CharacterList = root.Q<ListView>("CharacterList");

    // 選択されたキャラクターリスト要素への参照を保存
    m_CharClassLabel = root.Q<Label>("CharacterClass");
    m_CharNameLabel = root.Q<Label>("CharacterName");
    m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

    // 選択ボタンへの参照を保存n    m_SelectCharButton = root.Q<Button>("SelectCharButton");
}

リストにエントリーを入力

次に、先ほど列挙してロードしたキャラクターを画面のリストに入力する必要があります。そのためには、CharacterListController クラスの中に FillCharacterList という新しいメソッドを作成する必要があります。

ListView に要素を入力するには、4 つのステップが必要です。

  1. makeItem 関数を作成
  2. bindItem 関数を作成
  3. アイテムの高さを設定
  4. アイテムソースを設定

makeItem コールバック関数の目的は、UI を表す小さなビジュアルツリーを 1 つのリストアイテムで作成し、このツリーのルートの VisualElement を返すことです。

この場合、makeItem コールバックは、リストエントリー用に作成した UXML テンプレートをインスタンス化する必要があります。また、CharacterListEntryController コントローラースクリプトのインスタンスを作成する必要があります。これは、CharacterData からのデータで UI を埋める処理を行うものです。

クラス内に FillCharacterList メソッドを作成し、以下のコードを貼り付けてください。

void FillCharacterList()
{
    // リストエントリーの makeItem 関数を設定
    m_CharacterList.makeItem = () =>
    {
         // エントリー用の UXML テンプレートをインスタンス化
        var newListEntry = m_ListEntryTemplate.Instantiate();

      // データ用のコントローラーをインスタンス化
        var newListEntryLogic = new CharacterListEntryController();

        // コントローラースクリプトをビジュアル要素に割り当てる
        newListEntry.userData = newListEntryLogic;
    
        // コントローラースクリプトを初期化
       newListEntryLogic.SetVisualElement(newListEntry);

       // インスタンス化されたビジュアルツリーのルートを返す
        return newListEntry;
    };

makeItem コールバックの一部として、コントローラースクリプトを、インスタンス化したビジュアル要素の userData プロパティ内に格納します。これにより、後でスクリプトにアクセスし、リスト要素に異なるキャラクターを割り当てることができます。

// コントローラスクリプトをビジュアル要素に割り当てる
newListEntry.userData = newListEntryLogic;

メモリとパフォーマンスの最適化として、ListView は、リスト内のエントリーごとに 1 つの要素をインスタンス化するのではなく、リスト要素を再利用します。これは、可視領域を満たすのに十分なビジュアル要素のみを作成し、リストがスクロールされるとそれらをプールして再利用します。

このため、データのインスタンス (この場合は CharacterData) を個々のリスト要素に紐づけする bindItem コールバックを用意する必要があります。

FillCharacterList メソッドの下部に以下のコードを追加し、拡張します。

// 特定のリストエントリーに対するバインド関数を設定
m_CharacterList.bindItem = (item, index) =>
{
    (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
};

bindItem コールバックは、リストエントリーのビジュアルツリーのためのルートビジュアル要素への参照と、データへのインデックスを受け取ります。ビジュアル要素の userData プロパティに CharacterListEntryController への参照を保存したので、コードはこれにアクセスして直接CharacterData を設定できます。

最後に、要素のアイテムの高さを設定し、リストのデータソースへの参照を提供する必要があります。これは、リストに含まれる要素の数を伝えます。

FillCharacterList メソッドの下部に以下のコードを追加し、拡張します。

// 固定アイテムの高さを設定
m_CharacterList.fixedItemHeight = 45;

// 実際のアイテムのソースリスト/配列を設定
m_CharacterList.itemsSource = m_AllCharacters;

初期化の最後に FillCharacterList メソッドを呼び出す必要があります。 以下のように、 InitializeCharacterList メソッドの一番下に呼び出しを追加します。

FillCharacterList();

この時点で 再生モード にすると、キャラクターリストが作成したキャラクターの名前でいっぱいになります。

キャラクターリストが空でなくなりました
キャラクターリストが空でなくなりました

CharacterListController スクリプトの最終的な コード は、このガイドの下の方にあります。

ユーザーの選択への反応

ユーザーがキャラクターを選択すると、画面右側のキャラクター詳細欄に、キャラクターの詳細、つまり画像、フルネーム、クラスが表示される必要があります。また、キャラクターを選択すると、選択ボタンが有効になる必要があります。キャラクターが選択されていないときは、再びボタンが無効になるようにします。

なお、リスト内のキャラクターをクリックして選択することはすでに可能です。選択とハイライトの機能は、ListView コントロールの一部です。必要なのは、ユーザーがリストの選択を変更するときに反応するコールバック関数だけです。ListView コントロールは、この目的のために onSelectionChange イベントを含んでいます。

InitializeCharacterList メソッドの最後に以下のコードを加えます。

// アイテムが選択されたときにコールバックを取得するように登録
m_CharacterList.onSelectionChange += OnCharacterSelected;

ここで、上のコードで設定したコールバック関数 OnCharacterSelected を実装する必要があります。この関数は、リスト内で選択されたすべてのアイテムのリストを受け取ります。ただし、このリストでは 1 つのアイテムしか選択できないため、リストの selectedItem プロパティを使用して、選択されたアイテムに直接アクセスできます。

以下のコードをクラス内にコピーしてください。

void OnCharacterSelected(IEnumerable<object> selectedItems)
{
    // ListView から現在選択されているアイテムを直接取得
    var selectedCharacter = m_CharacterList.selectedItem as CharacterData;
}

selectedItem プロパティが null を返す場合があります。これは、何も選択されていない場合、またはユーザーが ESC キーを押してすべての選択を解除した場合に起こります。このケースは、最初に処理する必要があります。

OnCharacterSelected メソッドを以下のように拡張します。

void OnCharacterSelected(IEnumerable<object> selectedItems)
{
     // ListView から現在選択されているアイテムを直接取得
    var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

   // 非選択を処理 (Escape ですべて非選択にする)
    if (selectedCharacter == null)
    {
       // クリア
        m_CharClassLabel.text = "";
        m_CharNameLabel.text = "";
        m_CharPortrait.style.backgroundImage = null;

          // 選択ボタンを無効にする
        m_SelectCharButton.SetEnabled(false);

        return;
    }
}

選択が有効である場合、UI にキャラクターの詳細を表示する必要があります。クラスの InitializeCharacterList メソッドで取得した参照を通じて、ラベルとポートレート画像のビジュアル要素にアクセスできます。

以下のコードを OnCharacterSelected メソッドにコピーします。

// キャラクターの詳細を記入する
m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
m_CharNameLabel.text = selectedCharacter.m_CharacterName;
m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

// 選択ボタンを有効にする
m_SelectCharButton.SetEnabled(true);

これで 再生モード に入り、キャラクター選択リストの動作を確認することができます。Escape キーを押すと、キャラクターの選択を解除できます。

最終的なランタイム UI
最終的なランタイム UI

最終的なスクリプト

以下に、このガイドで作成したすべてのファイルの完全なソースコードを掲載します。

MainView.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement style="flex-grow: 1; align-items: center; justify-content: center; background-color: rgb(115, 37, 38);">
        <ui:VisualElement style="flex-direction: row; height: 350px;">
            <ui:ListView focusable="true" name="CharacterList" style="width: 230px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-left-width: 4px; border-right-width: 4px; border-top-width: 4px; border-bottom-width: 4px; background-color: rgb(110, 57, 37); border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; margin-right: 6px;" />
            <ui:VisualElement style="justify-content: space-between; align-items: flex-end;">
                <ui:VisualElement style="align-items: center; background-color: rgb(170, 89, 57); border-left-width: 4px; border-right-width: 4px; border-top-width: 4px; border-bottom-width: 4px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; width: 276px; justify-content: center; padding-left: 8px; padding-right: 8px; padding-top: 8px; padding-bottom: 8px;">
                    <ui:VisualElement style="border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; height: 120px; width: 120px; border-top-left-radius: 13px; border-bottom-left-radius: 13px; border-top-right-radius: 13px; border-bottom-right-radius: 13px; padding-left: 4px; padding-right: 4px; padding-top: 4px; padding-bottom: 4px; background-color: rgb(255, 133, 84);">
                        <ui:VisualElement name="CharacterPortrait" style="flex-grow: 1; -unity-background-scale-mode: scale-to-fit;" />
                    </ui:VisualElement>
                    <ui:Label text="Label" name="CharacterName" style="-unity-font-style: bold; font-size: 18px;" />
                    <ui:Label text="Label" display-tooltip-when-elided="true" name="CharacterClass" style="margin-top: 2px; margin-bottom: 8px; padding-top: 0; padding-bottom: 0;" />
                </ui:VisualElement>
                <ui:Button text="Select Character" display-tooltip-when-elided="true" name="SelectCharButton" style="width: 150px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); background-color: rgb(255, 133, 84); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px;" />
            </ui:VisualElement>
        </ui:VisualElement>
    </ui:VisualElement>
</ui:UXML>

ListEntry.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement style="height: 41px; align-items: flex-start; justify-content: center; padding-left: 10px; background-color: rgba(170, 89, 57, 255); border-left-color: rgba(49, 26, 17, 255); border-right-color: rgba(49, 26, 17, 255); border-top-color: rgba(49, 26, 17, 255); border-bottom-color: rgba(49, 26, 17, 255); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px;">
        <ui:Label text="Label" display-tooltip-when-elided="true" name="CharacterName" style="-unity-font-style: bold; font-size: 18px;" />
    </ui:VisualElement>
</ui:UXML>

CharacterData.cs

using UnityEngine;

public enum ECharacterClass
{
    Knight, Ranger, Wizard
}

[CreateAssetMenu]
public class CharacterData : ScriptableObject
{
    public string m_CharacterName;
    public ECharacterClass m_Class;
    public Sprite m_PortraitImage;
}

CharacterListEntryController.cs

using UnityEngine.UIElements;

public class CharacterListEntryController
{
    Label m_NameLabel;

    public void SetVisualElement(VisualElement visualElement)
    {
        m_NameLabel = visualElement.Q<Label>("CharacterName");
    }

    public void SetCharacterData(CharacterData characterData)
    {
        m_NameLabel.text = characterData.m_CharacterName;
    }
}

MainView.cs

using UnityEngine;
using UnityEngine.UIElements;

public class MainView : MonoBehaviour
{
    [SerializeField]
    VisualTreeAsset m_ListEntryTemplate;

    void OnEnable()
    {
        // UXML は、すでに UIDocument component コンポーネントによってインスタンス化済み
        var uiDocument = GetComponent<UIDocument>();

        // キャラクターリストコントローラーを初期化
        var characterListController = new CharacterListController();
        characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
    }
}

CharacterListController.cs

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

public class CharacterListController
{
     // リストエントリー用 UXML テンプレート
    VisualTreeAsset m_ListEntryTemplate;

    // UI 要素の参照
    ListView m_CharacterList;
    Label m_CharClassLabel;
    Label m_CharNameLabel;
    VisualElement m_CharPortrait;
    Button m_SelectCharButton;

    public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
    {
        EnumerateAllCharacters();

  // リストエントリーのテンプレートへの参照を保存
        m_ListEntryTemplate = listElementTemplate;

        // キャラクターリスト要素への参照を保存
        m_CharacterList = root.Q<ListView>("CharacterList");

          // 選択されたキャラクター情報要素への参照を保存
        m_CharClassLabel = root.Q<Label>("CharacterClass");
        m_CharNameLabel = root.Q<Label>("CharacterName");
        m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

        // 選択ボタンへの参照を保存
        m_SelectCharButton = root.Q<Button>("SelectCharButton");

        FillCharacterList();

         // アイテムが選択されたときのコールバックを取得するために登録
        m_CharacterList.onSelectionChange += OnCharacterSelected;
    }

    List<CharacterData> m_AllCharacters;

    void EnumerateAllCharacters()
    {
        m_AllCharacters = new List<CharacterData>();
        m_AllCharacters.AddRange(Resources.LoadAll<CharacterData>("Characters"));
    }

    void FillCharacterList()
    {
         // リストエントリーに対する make item 関数を設定
        m_CharacterList.makeItem = () =>
        {
             // エントリー用の UXML テンプレートをインスタンス化
            var newListEntry = m_ListEntryTemplate.Instantiate();

           // データ用のコントローラーをインスタンス化
            var newListEntryLogic = new CharacterListEntryController();

            // コントローラースクリプトをビジュアル要素に割り当てる
            newListEntry.userData = newListEntryLogic;

             // コントローラースクリプトを初期化            
     newListEntryLogic.SetVisualElement(newListEntry);

             // インスタンス化されたビジュアルツリーのルートを返す
            return newListEntry;
        };

     // 特定のリストエントリーに対するバインド関数を設定
        m_CharacterList.bindItem = (item, index) =>
        {
            (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
        };

  // アイテムの固定の高さを設定
        m_CharacterList.fixedItemHeight = 45;

       // 実際のアイテムのソースリスト/配列を設定
        m_CharacterList.itemsSource = m_AllCharacters;
    }

    void OnCharacterSelected(IEnumerable<object> selectedItems)
    {
        // ListView から現在選択されているアイテムを直接取得
        var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

             // 非選択を処理 (Escape ですべて非選択にする)
        if (selectedCharacter == null)
        {
            // クリア
            m_CharClassLabel.text = "";
            m_CharNameLabel.text = "";
            m_CharPortrait.style.backgroundImage = null;

            // 選択ボタンを無効にする
            m_SelectCharButton.SetEnabled(false);

            return;
        }

       // キャラクターの詳細を入力
        m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
        m_CharNameLabel.text = selectedCharacter.m_CharacterName;
        m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

       // 選択ボタンを有効にする
        m_SelectCharButton.SetEnabled(true);
    }
}
カスタムインスペクターの作成
ビジュアルツリー