Version: 2021.2
言語: 日本語
カスタムインスペクターの作成
Transition from Unity UI (UGUI) to UI Toolkit

Creating your first runtime UI

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

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

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

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

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

メイン UI ビューの作成

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

ノート
If you are familiar with UI Builder and want to skip this step, you can copy the UXML code for the main view from the bottom of this page and paste it into a new file directly. Save it as 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 コンポーネント
ノート
If you do not assign a PanelSettings asset to your UI Document component, it will automatically search the project and use the first Panel Settings Asset it finds automatically. Keep this in mind when renaming or moving assets.

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

ランタイムに表示される UI
ランタイムに表示される UI
ノート
If you have multiple UI Documents in your scene, you can assign the same panel settings asset to all. This will cause all UI to be rendered on the same panel, optimizing performance.

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

このセクションでは、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 は、色のついた背景フレームとキャラクター名で構成されています。

キャラクター名を表示するリストエントリー
キャラクター名を表示するリストエントリー
ノート
If you want to skip this step, you can copy the UXML code for the list entry from the bottom of this page and paste it into a new file directly. Save it as 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
{
  private Label m_NameLabel;

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

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

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

SetvisualElement(VisualElement visualElement) This function will receive a visual element that is an instance of the ListEntry UI template you created in the previous section. The main view controller will create this instance. The purpose of this function is to retrieve a reference to the character name label inside the UI element.

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] private VisualTreeAsset m_ListEntryTemplate;

  void OnEnable()
  {
    // The UXML is already instantiated by the UIDocument component
    var uiDocument = GetComponent<UIDocument>();

    // Initialize the character list controller
    var characterListController = new CharacterListController();
    characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
  }
}

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

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

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

ノート
When the UI is reloaded, companion MonoBehaviour components on the same GameOBject containing the UIDocument component will be disabled prior to the reload, and then re-enabled after the reload. Therefore it’s a good practice to place code that interacts with the UI in the OnEnable and OnDisable methods of these MonoBehaviours.

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

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

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

private List<CharacterData> m_AllCharacters;

private void EnumerateAllCharacters()
{
  m_AllCharacters = new List<CharacterData>();
  m_AllCharacters.AddRange(Resources.LoadAll<CharacterData>("Characters"));
}
ノート
This code assumes that you created the character instances in the Resources/Characters folder. You might need to adjust the folder name accordingly if you placed the characters in a different folder.

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

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  // Enumerate all characters
  EnumerateAllCharacters();
}

UI 要素への参照を取得

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

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

// UXML template for list entries
private VisualTreeAsset m_ListEntryTemplate;

// UI element references
private ListView m_CharacterList;
private Label m_CharClassLabel;
private Label m_CharNameLabel;
private VisualElement m_CharPortrait;
private Button m_SelectCharButton;

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  // Enumerate all characters
  EnumerateAllCharacters();

  // Store a reference to the template for the list entries
  m_ListEntryTemplate = listElementTemplate;

  // Store a reference to the character list element
  m_CharacterList = root.Q<ListView>("CharacterList");

  // Store references to the selected character info elements
  m_CharClassLabel = root.Q<Label>("CharacterClass");
  m_CharNameLabel = root.Q<Label>("CharacterName");
  m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

  // Store a reference to the select button
  m_SelectCharButton = root.Q<Button>("SelectCharButton");
}

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

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

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

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

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

In this case, the makeItem callback needs to instantiate the UXML template you created for the list entries. IT also needs to create an instance of the CharacterListEntryController controller script, which takes care of filling the UI with the data from the CharacterData.

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

private void FillCharacterList()
{
  // Set up a make item function for a list entry
  m_CharacterList.makeItem = () =>
  {
    // Instantiate the UXML template for the entry
    var newListEntry = m_ListEntryTemplate.Instantiate();

    // Instantiate a controller for the data
    var newListEntryLogic = new CharacterListEntryController();

    // Assign the controller script to the visual element
    newListEntry.userData = newListEntryLogic;
    
    // Initialize the controller script
    newListEntryLogic.SetVisualElement(newListEntry);

    // Return the root of the instantiated visual tree
    return newListEntry;
  };
}

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

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

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

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

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

private void FillCharacterList()
{
  ...

  // Set up bind function for a specific list entry
  m_CharacterList.bindItem = (item, index) =>
  {
    (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
  };
}

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

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

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

private void FillCharacterList()
{
  ...

  // Set a fixed item height
  m_CharacterList.fixedItemHeight = 45;
  // For Unity versions earlier than 2021.2 use this:
  //m_CharacterList.itemHeight = 45; 

  // Set the actual item's source list/array
  m_CharacterList.itemsSource = m_AllCharacters;
}

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

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  ...

  FillCharacterList();
}

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

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

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

ユーザーの選択への反応

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

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

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

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
  ...

  // Register to get a callback when an item is selected
  m_CharacterList.onSelectionChange += OnCharacterSelected;
}

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

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

private void OnCharacterSelected(IEnumerable<object> selectedItems)
{
  // Get the currently selected item directly from the ListView
  var selectedCharacter = m_CharacterList.selectedItem as CharacterData;
}

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

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

private void OnCharacterSelected(IEnumerable<object> selectedItems)
{
  // Get the currently selected item directly from the ListView
  var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

  // Handle none-selection (Escape to deselect everything)
  if (selectedCharacter == null)
  {
    // Clear
    m_CharClassLabel.text = "";
    m_CharNameLabel.text = "";
    m_CharPortrait.style.backgroundImage = null;

    // Disable the select button
    m_SelectCharButton.SetEnabled(false);

    return;
  }
}

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

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

private void OnCharacterSelected(IEnumerable<object> selectedItems)
{
  ...

  // Fill in character details
  m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
  m_CharNameLabel.text = selectedCharacter.m_CharacterName;
  m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

  // Enable the select button
  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
{
  private 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] private VisualTreeAsset m_ListEntryTemplate;

  void OnEnable()
  {
    // The UXML is already instantiated by the UIDocument component
    var uiDocument = GetComponent<UIDocument>();

    // Initialize the character list controller
    var characterListController = new CharacterListController();
    characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
  }
}

CharacterListController.cs

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

public class CharacterListController
{
  private List<CharacterData> m_AllCharacters;

  // UXML template for list entries
  private VisualTreeAsset m_ListEntryTemplate;

  // UI element references
  private ListView m_CharacterList;
  private Label m_CharClassLabel;
  private Label m_CharNameLabel;
  private VisualElement m_CharPortrait;
  private Button m_SelectCharButton;

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

  public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
  {
    // Enumerate all characters
    EnumerateAllCharacters();

    // Store a reference to the template for the list entries
    m_ListEntryTemplate = listElementTemplate;

    // Store a reference to the character list element
    m_CharacterList = root.Q<ListView>("CharacterList");

    // Store references to the selected character info elements
    m_CharClassLabel = root.Q<Label>("CharacterClass");
    m_CharNameLabel = root.Q<Label>("CharacterName");
    m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

    // Store a reference to the select button
    m_SelectCharButton = root.Q<Button>("SelectCharButton");

    FillCharacterList();

    // Register to get a callback when an item is selected
    m_CharacterList.onSelectionChange += OnCharacterSelected;
  }

  private void OnCharacterSelected(IEnumerable<object> selectedItems)
  {
    // Get the currently selected item directly from the ListView
    var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

    // Handle none-selection (Escape to deselect everything)
    if (selectedCharacter == null)
    {
      // Clear
      m_CharClassLabel.text = "";
      m_CharNameLabel.text = "";
      m_CharPortrait.style.backgroundImage = null;

      // Disable the select button
      m_SelectCharButton.SetEnabled(false);

      return;
    }

    // Fill in character details
    m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
    m_CharNameLabel.text = selectedCharacter.m_CharacterName;
    m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

    // Enable the select button
    m_SelectCharButton.SetEnabled(true);
  }

  private void FillCharacterList()
  {
    // Set up a make item function for a list entry
    m_CharacterList.makeItem = () =>
    {
      // Instantiate the UXML template for the entry
      var newListEntry = m_ListEntryTemplate.Instantiate();

      // Instantiate a controller for the data
      var newListEntryLogic = new CharacterListEntryController();

      // Assign the controller script to the visual element
      newListEntry.userData = newListEntryLogic;

      // Initialize the controller script
      newListEntryLogic.SetVisualElement(newListEntry);

      // Return the root of the instantiated visual tree
      return newListEntry;
    };

    // Set up bind function for a specific list entry
    m_CharacterList.bindItem = (item, index) =>
    {
      (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
    };

    // Set a fixed item height
    m_CharacterList.fixedItemHeight = 45;
    // For Unity versions earlier than 2021.2 use this:
    //m_CharacterList.itemHeight = 45; 

    // Set the actual item's source list/array
    m_CharacterList.itemsSource = m_AllCharacters;
  }
}
カスタムインスペクターの作成
Transition from Unity UI (UGUI) to UI Toolkit