バージョン: 2021.3 以降
タブ付きメニューは、ビデオゲームやアプリケーションの UI でよく見られます。UI Toolkit を使用すると、カスタムエディターウィンドウやランタイムにタブ付きメニューを作成することができます。この例では、サンプルシーンにタブ付きメニューを作成する方法を説明します。
この例では、サンプルシーンにメニューを加えます。メニューには 3 つのタブがあります。各タブには、特定のコンテンツが表示されます。タブをクリックすると、そのタブに関連するコンテンツのみが表示されます。
タブ付きコンテンツを作成するには、メニューの要素、スタイル、ロジックを定義する必要があります。
この例で作成するすべてのファイルは、GitHub リポジトリ にあります。
このガイドは、Unity、UI Toolkit、Flexbox、および C# スクリプトに精通している開発者向けです。始める前に、以下を理解しておいてください。
UI Builder を使用して、メニューに 2 つのセクション (タブ用とタブのコンテンツ用) を作成します。これらのセクションに、3 つのタブ要素と 3 つのタブコンテンツ要素を作成します。
Unity で任意のテンプレートでプロジェクトを作成します。
GameObject > UI Toolkit > UI Document の順にクリックして、サンプルシーンに UI ドキュメントを加えます。
Assets
に TabbedMenu
という名前のフォルダーを作成し、すべてのファイルを保存します。
TabbedMenu
フォルダーに、TabbedMenu.uxml
という名前の UI ドキュメントを作成します。
TabbedMenu.uxml
をダブルクリックして、UI Builder で開きます。
ルートの下にtabs
と tabContent
という 2 つの VisualElement を加えます。
tabs
の下に 3 つのラベルコントロールを加え、それらに以下のラベルテキストを設定します。
London
Paris
Ottawa
tabContent
の下に 3 つのラベルコントロールを加え、それらに以下のラベルテキストを設定します。
London is the capital city of England
(London は英国の首都)Paris is the capital of France
(Paris はフランスの首都)Ottawa is the capital of Canada
(Ottawa はカナダの首都)タブの内容をタブに関連付けるため、この例ではラベル名に同じプレフィックスと異なるサフィックスを使用しています。各タブ名には Tab
というサフィックスを、各タブコンテンツには Content
というサフィックスを付けています。タブのラベル名とコンテンツのラベル名を以下のように設定します。
LondonTab
ParisTab
OttawaTab
LondonContent
ParisContent
OttawaContent
UI Builder の Hierarchy は以下のようになります。
USS を使って、タブとタブコンテンツのレイアウトを定義します。タブとタブコンテンツは、好きなようにスタイルを設定することができます。この例では、タブを一列に並べ、その上にタブコンテンツを配置しています。また、選択されたタブの背景色を追加し、選択されていないタブの内容を非表示にします。
TabbedMenu
フォルダーに、TabbedMenu.uss
という名前のスタイルシートを作成します。
TabbedMenu.uss
を開き、以下のスタイルルールを加えます。
/* Style for tabs */
#tabs {
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 {
background-color: rgb(255, 255, 255);
font-size: 20px;
}
/* Hides the unselected tab content */
.unselectedContent {
display: none;
}
UI Builder で TabbedMenu.uxml
を開きます。
Add Existing USS をクリックし、TabbedMenu.uss
を選択します。
作成したスタイルを UI コントロールに適用します。
tabs
の下にある各ラベルに .tab
を適用します。.currentlySelectedTab
を LondonTab
に適用します。.unselectedContent
を ParisContent
と OttawaContent
に適用します。完成した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>
<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:VisualElement>
</ui:UXML>
サンプルシーン内に UIDocument ゲームオブジェクトを作成し、UI Document をソースアセットとして加えます。
表示されるタブの内容を変更する C# スクリプトを作成します。ユーザーがタブをクリックすると、タブの内容が表示され、現在の内容は非表示になります。タブメニューのロジックをゲームにアタッチする MonoBehaviour スクリプトを作成します。
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));
}
}
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();
}
}
サンプルシーンで UIDocument を選択し、Inspector の Add Component に TabbedMenu.cs
をドラッグします。
再生モードを開始し、いろいろなタブをクリックしてさまざまなコンテンツを確認します。