Version: 2022.1
言語: 日本語
スクロールビュー内でコンテンツを折り返す
ポップアップウィンドウの作成

ランタイム用タブメニューの作成

Version: 2021.3+

タブ付きメニューは、ビデオゲームやアプリケーションの UI でよく見られます。UI Toolkit を使用すると、カスタムエディターウィンドウやランタイムにタブ付きメニューを作成することができます。この例では、サンプルシーンにタブ付きメニューを作成する方法を説明します。

例の概要

この例では、サンプルシーンにメニューを加えます。メニューには 3 つのタブがあります。各タブには、特定のコンテンツが表示されます。タブをクリックすると、そのタブに関連するコンテンツのみが表示されます。

タブメニューのプレビュー
タブメニューのプレビュー

タブ付きコンテンツを作成するには、メニューの要素、スタイル、ロジックを定義する必要があります。

この例で作成するすべてのファイルは、GitHub リポジトリ にあります。

要件

このガイドは、Unity、UI Toolkit、Flexbox、および C# スクリプトに精通している開発者向けです。始める前に、以下を理解しておいてください。

タブメニューの要素を定義する

UI Builder を使用して、メニューに 2 つのセクション (タブ用とタブのコンテンツ用) を作成します。これらのセクションに、3 つのタブ要素と 3 つのタブコンテンツ要素を作成します。

  1. Unity で任意のテンプレートでプロジェクトを作成します。

  2. GameObject > UI Toolkit > UI Document の順にクリックして、サンプルシーンに UI ドキュメントを加えます。

  3. AssetsTabbedMenu という名前のフォルダーを作成し、すべてのファイルを保存します。

  4. TabbedMenuフォルダーに、TabbedMenu.uxml という名前の UI ドキュメントを作成します。

  5. TabbedMenu.uxml をダブルクリックして、UI Builder で開きます。

  6. ルートの下にtabstabContent という 2 つの VisualElement を加えます。

  7. tabs の下に 3 つのラベルコントロールを加え、それらに以下のラベルテキストを設定します。

    • London
    • Paris
    • Ottawa
  8. tabContent の下に 3 つのラベルコントロールを加え、それらに以下のラベルテキストを設定します。

    • London is the capital city of England (London は英国の首都)
    • Paris is the capital of France (Paris はフランスの首都)
    • Ottawa is the capital of Canada (Ottawa はカナダの首都)
  9. タブの内容をタブに関連付けるため、この例ではラベル名に同じプレフィックスと異なるサフィックスを使用しています。各タブ名には Tab というサフィックスを、各タブコンテンツには Content というサフィックスを付けています。タブのラベル名とコンテンツのラベル名を以下のように設定します。

    • LondonTab
    • ParisTab
    • OttawaTab
    • LondonContent
    • ParisContent
    • OttawaContent

UI Builder の Hierarchy は以下のようになります。

UI Builder の Hierarchy
UI Builder の Hierarchy

タブメニュースタイルを定義する

USS を使って、タブとタブコンテンツのレイアウトを定義します。タブとタブコンテンツは、好きなようにスタイルを設定することができます。この例では、タブを一列に並べ、その上にタブコンテンツを配置しています。また、選択されたタブの背景色を追加し、選択されていないタブの内容を非表示にします。

  1. TabbedMenu フォルダーに、TabbedMenu.uss という名前のスタイルシートを作成します。

  2. TabbedMenu.uss を開き、以下のスタイルルールを加えます。

    /* Style for tabs */
    #tabs {
        flex-basis: 10%;
        flex-direction: row;
        background-color: rgb(229, 223, 223);
        -unity-font-style: bold;
        font-size: 14px;
    }
    
    /* Sets each label in tabs to have the same size. */
    .tab {
        flex-grow: 1;
    }
    
    /* Adds background color for the selected tab */
    .currentlySelectedTab {
        background-color: rgb(173, 166, 166);
    }
    
    /* Style for tabContent */
    #tabContent {
        flex-basis: 90%;
        background-color: rgb(255, 255, 255);
        font-size: 20px;
    }
    
    /* Hides the unselected tab content */
    .unselectedContent {
        display: none;
    }
    
  3. UI Builder で TabbedMenu.uxml を開きます。

  4. Add Existing USS をクリックし、TabbedMenu.uss を選択します。

  5. 作成したスタイルを UI コントロールに適用します。

    • tabs の下にある各ラベルに .tab を適用します。
    • .currentlySelectedTabLondonTab に適用します。
    • .unselectedContentParisContentOttawaContent に適用します。

完成したTabbedMenu.uxml は以下のようになります。

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
   /* Your src might look different. If you save your UXML in UI Builder, USS file is referenced by the file location, fileID and guid. */
   <Style src="TabbedMenu-style.uss" />
   <ui:VisualElement name="tabs">
      <ui:Label name="LondonTab" text="London" class="tab currentlySelectedTab" />
      <ui:Label name="ParisTab" text="Paris" class="tab" />
      <ui:Label name="OttawaTab" text="Ottawa" class="tab" />
   </ui:VisualElement>
   <ui:VisualElement name="tabContent">
      <ui:Label text="London is the capital city of England" name="LondonContent" />
      <ui:Label text="Paris is the capital of France" name="ParisContent" class="unselectedContent" />
      <ui:Label text="Ottawa is the capital of Canada" name="OttawaContent" class="unselectedContent" />
   </ui:VisualElement>
</ui:UXML>

シーンの設定

サンプルシーン内に UIDocument ゲームオブジェクトを作成し、UI Document をソースアセットとして加えます。

  1. サンプルシーンで GameObject > UI Toolkit > UI Document の順にクリックします。
  2. Hierarchy でUIDocument ゲームオブジェクトを選択し階層で選択し、Project ウィンドウの TabbedMenu.uxml を Inspector ウィンドウの UI Document コンポーネントの Source Asset フィールドにドラッグします。これで、ソースアセットが作成した UXML ファイルに参照されます。

タブメニューのロジックを定義する

Create the C# scripts that change the displayed tab content. When a user clicks a tab, the tab’s content displays and the current content hides.

  1. TabbedMenu フォルダーに、以下の内容の C# スクリプトを作成し TabbedMenu.cs と命名します。

    // This script attaches the tabbed menu logic to the game.
    using UnityEngine;
    using UnityEngine.UIElements;
    
    //Inherits from class `MonoBehaviour`. This makes it attachable to a game object as a component.
    public class TabbedMenu : MonoBehaviour
    {
        private TabbedMenuController controller;
    
        private void OnEnable()
        {
            UIDocument menu = GetComponent<UIDocument>();
            VisualElement root = menu.rootVisualElement;
    
            controller = new(root);
    
            controller.RegisterTabCallbacks();
        }
    }
    
  2. サンプルシーンで UIDocument を選択し、Inspector の Add ComponentTabbedMenu.cs をドラッグします。

  3. TabbedMenu フォルダーに、以下の内容の C# スクリプトを作成し TabbedMenuController.cs と命名します。

    // This script defines the tab selection logic.
    using UnityEngine.UIElements;
    
    public class TabbedMenuController
    {
        /* Define member variables*/
        private const string tabClassName = "tab";
        private const string currentlySelectedTabClassName = "currentlySelectedTab";
        private const string unselectedContentClassName = "unselectedContent";
        // Tab and tab content have the same prefix but different suffix
        // Define the suffix of the tab name
        private const string tabNameSuffix = "Tab";
        // Define the suffix of the tab content name
        private const string contentNameSuffix = "Content";
    
        private readonly VisualElement root;
    
        public TabbedMenuController(VisualElement root)
        {
            this.root = root;
        }
    
        public void RegisterTabCallbacks()
        {
            UQueryBuilder<Label> tabs = GetAllTabs();
            tabs.ForEach((Label tab) => {
                tab.RegisterCallback<ClickEvent>(TabOnClick);
            });
        }
    
        /* Method for the tab on-click event: 
    
           - If it is not selected, find other tabs that are selected, unselect them 
           - Then select the tab that was clicked on
        */
        private void TabOnClick(ClickEvent evt)
        {
            Label clickedTab = evt.currentTarget as Label;
            if (!TabIsCurrentlySelected(clickedTab))
            {
                GetAllTabs().Where(
                    (tab) => tab != clickedTab && TabIsCurrentlySelected(tab)
                ).ForEach(UnselectTab);
                SelectTab(clickedTab);
            }
        }
        //Method that returns a Boolean indicating whether a tab is currently selected
        private static bool TabIsCurrentlySelected(Label tab)
        {
            return tab.ClassListContains(currentlySelectedTabClassName);
        }
    
        private UQueryBuilder<Label> GetAllTabs()
        {
            return root.Query<Label>(className: tabClassName);
        }
    
        /* Method for the selected tab: 
           -  Takes a tab as a parameter and adds the currentlySelectedTab class
           -  Then finds the tab content and removes the unselectedContent class */
        private void SelectTab(Label tab)
        {
            tab.AddToClassList(currentlySelectedTabClassName);
            VisualElement content = FindContent(tab);
            content.RemoveFromClassList(unselectedContentClassName);
        }
    
        /* Method for the unselected tab: 
           -  Takes a tab as a parameter and removes the currentlySelectedTab class
           -  Then finds the tab content and adds the unselectedContent class */
        private void UnselectTab(Label tab)
        {
            tab.RemoveFromClassList(currentlySelectedTabClassName);
            VisualElement content = FindContent(tab);
            content.AddToClassList(unselectedContentClassName);
        }
    
        // Method to generate the associated tab content name by for the given tab name
        private static string GenerateContentName(Label tab) =>
            tab.name.Replace(tabNameSuffix, contentNameSuffix);
    
        // Method that takes a tab as a parameter and returns the associated content element
        private VisualElement FindContent(Label tab)
        {
            return root.Q(GenerateContentName(tab));
        }
    }
    
  4. 再生モードを開始し、いろいろなタブをクリックしてさまざまなコンテンツを確認します。

その他の参考資料

スクロールビュー内でコンテンツを折り返す
ポップアップウィンドウの作成