Search Unity

UI ToolKitを導入して効率よくUIを構築する

  • ゲーム開発
  • 開発者向け

<このページで学べる内容>

Unity2021.2からゲームUIとしても利用できるようになったUI ToolKit。本記事ではuGUIと比較しながら開発フローがどう変化するのか、UI ToolKitをどう活用できるのかについて知ることができます。

検証環境

  • Unity 2021.2.3f1

対象読者

この記事ではUI ToolKitの導入を迷っている方やuGUIとの使い分けを検討している方に向けて、どういうメリット/デメリットがあるのか、どうuGUIと連携するのかについて解説していきます。この記事が導入のきっかけになれば幸いです。

UI ToolKitとは

UI ToolKitは「キット」と名のつく通り、GUIを開発するための機能、リソース、ツールを集めたツールキットです。実はこの機能はUnity2019.1時点でUI Elementsという名前で提供されていました。ただこのときのバージョンはエディタ拡張のGUI構築用ツールとして提供されていて、ゲームUIとしては使用できませんでした。しかしアップデートによりゲームUIでも利用できるようになったのがこのUI ToolKitです。

UI ToolKitが出るまではuGUIがゲームUIを作成する一般的な方法でしたが、UI ToolKitの登場により選択肢が広がっています。

Webのパラダイムを利用している

UI ToolKitでのUI構築フローは従来のuGUIのフローと比べて大きく変わっています。UI構築はXMLをベースとしたUXML、CSSをベースとしたUSSを利用してUIの定義、レイアウトをしていきます。Web開発をしたことがある方であれば見慣れた方法でUIを構築することが可能です。

以下にサンプルを示します。HTMLで情報を記述するのにとても似ていることが分かるでしょうか。

<ui:UXML xmlns:ui="UnityEngine.UIElements">
    <ui:VisualElement>
        <ui:Label text="これはラベルです" display-tooltip-when-elided="true" />
        <ui:Button text="ボタン" display-tooltip-when-elided="true" class="button" />
    </ui:VisualElement>
</ui:UXML>

このUXMLを実際に表示すると以下のようになります。

uGUIとの違い

uGUIとの大きな違いは制作フローの変化にあります。uGUIは基本的に各要素がどれも GameObject でした。言い換えると各要素はヒエラルキーに存在していました。しかしUI ToolKitの各要素はヒエラルキーには存在せず、あくまでUXML内に定義された要素が画面に描画されます。

パラダイムとしてはWeb開発とほぼ同じものとなるため、UIの意味的な定義をUXMLで行い、レイアウトや見た目についてはUSSで行います。一方、uGUIでは各要素の GameObject にUIの意味と見た目を同時に設定しなければなりませんでした。しかしUI ToolKitではそれぞれを分離できるだけでなく、定義をファイル単位で分割することができます。CSSのメリットである「見た目をCSSの読み込み先によって変える」といった使い方も可能となります。

UI ToolKitを使うメリット

uGUIとは大きく異なる制作フローを持つUI ToolKit。ここでは、uGUIと比べてどういったメリットがあるのかを解説します。

Web開発のノウハウが活かせる

Web開発のノウハウがかなりの部分で活かせます。HTML/CSSを用いたデザインや制作フローなどもそのまま活かせるでしょう。特に、Web開発経験があるエンジニア、デザイナーであればそのままその知識を利用することもできます。執筆者自身も過去はWebエンジニアだった経験もあり、そのときの知識を利用して理解、実装をすることができました。さらに、もしWeb開発経験があるデザイナーであれば、コードレベルで共同作業ができるかもしれません。

移植性/運用性の向上

UXML/USSともにどちらもテキストベースで記述します。そのため別プロジェクトのコードやサンプルコードなどをそのまま持ってくることが可能です。一方、uGUIの場合はPrefabやシーンに情報が保存されており、大部分が構造に依存してしまっていました。しかしUI ToolKitならば構造がUXMLに閉じるためそうしたPrefabやシーンの構造の依存を気にする必要がなくなります。

テキストベースの移植性の向上はGitなどのバージョン管理ツールとも相性がよく、仮にコンフリクトを起こしたとしても修正は容易です。プログラムコードではたまに発生する、過去のバージョンに戻して開発を続けるといったことも可能となり、よりかんたんにUIを管理することが可能となります。

デザイン性の向上

WebのCSSと同様のデザイン性を持っているため、uGUIでは実現できなかった、あるいは実現が面倒だったUIについてもかんたんに構築することができます。例えば角丸のボーダーを用いたデザインなどは、uGUIでは画像を用意して背景にするような準備が必要でした。しかしUSSでは以下のような記述で実現できます。

.button {
    background-color: gray;
    border-radius: 15px;
    border-color: white;
    border-width: 5px;
}

これを実際に表示すると以下のようになります。

CSSベースのアニメーション定義ができる

デザイン性の向上はアニメーションにも適用することができます。CSSにはトランジションという仕組みがあり、状態の変化に応じて自動的に値を補完するアニメーションを定義することができます。

例えば、通常状態は白い背景でHover状態は薄いブルー、選択状態は濃いブルーといったことを事前に定義しておき、トランジションの設定(時間など)をしておくだけで自動的にアニメーションを実行してくれます。

以下のようなUSSを定義して実際に実行してみます。

.selectable-button {
    background-color: white;
    border-color: black;
    border-width: 2px;
    transition-duration: 0.3s;
    transition-timing-function: ease-in-out-cubic;
}

.selectable-button:hover {
    background-color: rgb(137, 186, 215);
}

.selectable-button.selected {
    color: #eee;
    background-color: #006BAB;
    border-color: white;
}

動作イメージは以下の通りです。トランジションの定義をしておくだけで、状態が切り替わる際に自動的にアニメーションしているのが確認できます。

GUI(UI Builder)を使ってUIを構築できる

UI ToolKitはUI Builderという、GUI上でUIを構築できるツールを提供しています。ドキュメントから画像を引用すると以下のようなウィンドウで編集することができます。

このツールではUIの階層構造や各スタイルなど様々な情報を可視化でき、ドラッグ&ドロップで移動なども行えます。さらに、UXMLをエディタで編集するとそれが即座に反映されます。こうしたことから、デザイナーとエンジニアがそれぞれ得意な方法でUIを編集、構築していくことができます。

UI ToolKitを使うデメリット

UI ToolKitを使うデメリットも存在します。現状、すべてのuGUIの機能をUI ToolKitに移植できるわけではありません。以下ではいくつかのデメリットも合わせてご紹介します。

パーツ単位でPrefab化できない

UI ToolKitでは UIDocument という単位で管理されており、前述のように、ヒエラルキーに置かれるわけではありません。そのため、パーツごとにPrefab化して保存することができなくなっています。ただ、UI ToolKitにはテンプレートという機能があり、UXMLを断片的に定義しておきそれを組み合わせるという使い方ができるため、プロジェクト次第ではあまりデメリットにはならないかもしれません。

シリアライズフィールドに個別パーツを設定できない

前述のPrefab化できない問題と似た話ではありますが、ボタンなど個別パーツがヒエラルキー上に存在しないため、今までのuGUIのフローであったような、SerializeField にボタンを登録してセットアップという方法が使えなくなっています。そのため、パーツ単位の管理用スクリプトを作るなどして対応する必要が出てくる可能性があり、プロジェクト規模や確立されているフローと合わない場合は導入がむずかしくなるかもしれません。

ワールド空間に配置することができない

現在のバージョンではUI ToolKitのUIをワールド空間に配置することができません。いずれは対応されるとは思いますが、ワールド空間上にUIを配置しているプロジェクトの場合は導入がむずかしいかもしれません。

ただ仕組み上は現時点でもワールド空間に配置することが可能です。が、UIを一度 RenderTexture に描画しそれをテクスチャとして表示、というような、やや力技での対応となるため、多数のUIをワールド空間に配置しているプロジェクトとは相性が悪いでしょう。特に、XRコンテンツの場合は必ずワールド空間に置かなければならない上に、この方法を持ってしても、Z軸方向に動かすようなUIの構築は不可能です。

なお、このワールド空間に置く方法についてコードを公開されている方がおり、またそれについてUnity Forumにも改善などを投稿されているので興味がある方はこちらを参考にしてみてください。

▼ Unity Forum

You didn't account for some use cases in the event dispatcher (VR/AR)

▼ サンプルコードの紹介

Here is a script to use UIToolkit in runtime worldspace

UI ToolKitでの制作工程紹介

ここでは、実際にUI ToolKitを使ってUIを構築していく工程をかんたんに紹介したいと思います。uGUIと比べてどう変わるのかのイメージを持ってもらえればと思います。

UI ToolKit用のファイルを用意する

Unity 2021.2ではデフォルトでUI ToolKitがインストールされているため、メニューから選択することでファイルを作成することができます。

まずはUXMLファイルを作成しましょう。Projectペインの「+」ボタンから UI ToolKit > UI Document を選択してUXMLファイルを作成します。

作成したUXMLに適当に名前をつけます。(今回の例では FirstUXML.uxml としました)

UI Builderで編集する

作成したUXMLファイルをダブルクリックすると自動的にUI Builderが起動します。デフォルトでは中央部分が実際のUIのプレビューになっていて、右側がインスペクタ、左側がヒエラルキーなどの情報になっています。

左下(図の赤枠)にはデフォルトで定義されているUIパーツが並んでいます。これをドラッグ&ドロップすることでUIを配置し、インスペクタから値を編集する、というのが大まかなUI構築の流れになります。

試しに Label Button をViewportに配置してみた図が以下です。

インスペクタには選択している Label の内容が表示されています。ここを変更することで内容を修正していくことができます。

uGUIと異なり、配置したUIの順番に自動でレイアウトされていることが確認できます。( Label の下に Button が配置され、親要素いっぱいに広がっている)

uGUIでは配置した順番は描画の前後関係には影響を与えますが、こうしたレイアウトには影響を与えていませんでした。こうした点もuGUIとの違いになります。

USSを追加する

UXMLを追加したら次はUSSを追加してみましょう。

※ 前述したように、要素の定義と見た目を切り離すことができるのがUI ToolKitを使う大きなメリットです。

UXMLを開いているUI Builderの StyleSheets ペインの「+」ボタンを押して Create New USS を選択します。するとファイル保存のダイアログが開くので、名前を付けて適当な場所に保存します。

するとUSSファイルが生成され、UXMLファイルと関連付けられてリストに表示されるようになります。

スタイルを適用しUSSに設定を書き出す

UI Builderで指定したスタイル(見た目)はそのままではUSSには書き出されません。これは、スタイルが各要素のプロパティとしてUXMLに書き込まれるため です。完全にひとつの要素にのみ適用されるスタイルであればそれでも問題になりませんが、UIのテンプレートとして他のUXMLの要素にも同じ見た目を適用したい場合はUSSに定義を書き出しておく必要があります。

書き出すには要素に class名 を付けて書き出します。

※ この class名 はC#のクラスとは別なので注意してください。

試しにボタンに以下のようなスタイルを適用してみました。

背景色、ボーダー、高さを設定した状態です。

インスペクタを見るとUnityのPrefabの変更と似たような形で、変更されている箇所の左側に白いラインが入っています。これはUSSで定義されたものではなく、個別に設定されているものを示しています。これをUSSに書き出してみましょう。

設定を書き出すにはUI Builderの Style Class List > Extract Inlined Styles to New Class を利用して書き出します。書き出す際に対象となる class名 を記入します。

実行してUSSファイルをテキストエディタなどで開いてみると、以下のように設定が書き出されているのが確認できます。

.sample-button {
    background-color: rgb(24, 109, 204);
    border-left-color: rgb(255, 255, 255);
    border-right-color: rgb(255, 255, 255);
    border-top-color: rgb(255, 255, 255);
    border-bottom-color: rgb(255, 255, 255);
    border-left-width: 2px;
    border-right-width: 2px;
    border-top-width: 2px;
    border-bottom-width: 2px;
    border-top-left-radius: 5px;
    border-bottom-left-radius: 5px;
    border-top-right-radius: 5px;
    border-bottom-right-radius: 5px;
    height: 65px;
}

当然、テキストで編集したものは即座にUI Builderに反映されるので、CSSが分かる人であれば直接記入しても問題ありません。

ちなみにUXML側をテキストエディタで開くと以下のように定義され、USSとの紐づけが確認できます。

<Style src="project://database/Assets/UI/FirstUSS.uss?fileID=7433441132597879392&amp;guid=7c0b9ea2cb5d39a45843fd6e02d0e06f&amp;type=3#FirstUSS" />

UIをゲームビューに表示する

最後に、今定義したUIをゲームビューに表示してみましょう。uGUIと異なり、定義しただけでは画面に表示されません。表示するためにはいくつかのコンポーネントとファイルを組み合わせる必要があります。

準備するもの/手順は以下です。

  • Panel Settings ファイル
  • UI Document コンポーネントをアタッチした GameObject
  • 上記 UI Document に、UXMLファイルと Panel Settings ファイルを設定

最初の Panel Settings はファイルのため、Projectペインから生成します。この Panel Settings はScale Modeやスタイルシートテーマの設定など描画に関する設定を保持するアセットです。

※ UIを RenderTexture に書き出す機能もあり、その場合は Target TextureフィールドRenderTexture を設定します。

次に、ヒエラルキーに空の GameObject を配置し、それに UI Document コンポーネントをアタッチします。

UI Document コンポーネントの Panel SettingsSource Asset にそれぞれ作成した Panel Settingsファイル UXMLファイル を設定します。

それぞれ設定するとゲームビューにUIが表示されるようになります。

UI Builderのプレビューに表示されていたUIがゲームビューにも表示されました。

大まかなUI ToolKitによるUI構築の流れは以上です。ここからは実際にデザインされたUIをUXMLとUSSを用いて構築していくことになります。

uGUIのフローとUI ToolKitによるフローの比較

制作フローがだいぶ違うことから、読者の中には「どうやってuGUIから移行しようか」と考えている方もいると思います。まったく同じ機能はないのであくまで似たようなことを実現するためには、という視点ですが、uGUIのフローでありそうなものを、どうしたらUI ToolKitで実現できるかという視点でいくつか機能を紹介します。

Prefab Variantのようにテンプレート化する

uGUIの場合はすべてが GameObject で構成されているためPrefab Variantなどを利用してテンプレート化を図るかと思います。UI ToolKitではこうしたテンプレート化を行うために、その名の通り Template という機能があります。

UXMLをテンプレートとして定義し、それを別のUXMLに埋め込むことでパーツとして読み込むことができます。さらに AttributeOverrides という機能を利用することで、UXML内の特定の要素の内容を書き換えることができます。

UXMLをテンプレート化する

まずUXMLファイルを作成し、テンプレートとなる要素を定義します。今回の例では以下のようにしました。

<ui:UXML xmlns:ui="UnityEngine.UIElements">
  <ui:Label text="Label base" name="label-test" />
  <ui:Button text="Button base" name="button-test" />
</ui:UXML>

そして利用する側のUXMLでテンプレートの宣言とインスタンス化を記述します。

<ui:UXML xmlns:ui="UnityEngine.UIElements">
    <ui:Template name="ButtonTemplate" src="./ButtonTemplate.uxml" />
    <ui:Instance template="ButtonTemplate">
        <ui:AttributeOverrides element-name="label-test" text="This override a text" />
        <ui:AttributeOverrides element-name="button-test" text="This override a button text" />
    </ui:Instance>
</ui:UXML>

ポイントは3つ。

ひとつめは <ui:Template /> タグで、これを利用してテンプレートを宣言します。宣言にはテンプレート名とテンプレートとなるUXMLのパスを指定します。

ふたつめは <ui:Instance> タグで、テンプレートを埋め込みたい箇所でインスタンス化します。

みっつめは <ui:AttributeOverrides /> タグで、内容を上書き(オーバーライド)したい要素を指定して内容を書き換えます。これを <ui:Instance> の入れ子にすることで対象のインスタンスが持つ要素を書き換えることができます。

以上の手順でUIパーツをテンプレート化しつつ、必要に応じて内容を書き換えることができます。

カスタム要素を定義する

前述のテンプレート化はUIの中身の変更差分を管理する方法でした。一方こちらは、C#でクラスを定義することで、UI Builderなどで利用できる共通パーツを作成します。

ここでは以下のプロパティを持つカスタム要素を定義してみます。

  • 名前(TargetName)
  • 歳(Age)
  • 体重(Weight)

※ 分かりやすさのためにあえてUI要素っぽい名前のない定義にしています。

まずはざっと実装を見てみましょう。

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

public class CustomElement : VisualElement
{
    public new class UxmlFactory : UxmlFactory<CustomElement, UxmlTraits>
    {
    }

    public new class UxmlTraits : VisualElement.UxmlTraits
    {
        private UxmlStringAttributeDescription _stringAttribute = new UxmlStringAttributeDescription
        {
            name = "TargetName",
            defaultValue = "--",
        };

        private UxmlIntAttributeDescription _intAttribute = new UxmlIntAttributeDescription
        {
            name = "Age",
            defaultValue = 0,
        };

        private UxmlFloatAttributeDescription _floatAttribute = new UxmlFloatAttributeDescription
        {
            name = "Weight",
            defaultValue = 0f,
        };

        public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
        {
            get { yield break; }
        }

        public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
        {
            base.Init(ve, bag, cc);

            if (!(ve is CustomElement element)) return;
           
            element.TargetName = _stringAttribute.GetValueFromBag(bag, cc);
            element.Age = _intAttribute.GetValueFromBag(bag, cc);
            element.Weight = _floatAttribute.GetValueFromBag(bag, cc);
        }
    }

    public string TargetName { get; set; }
    public int Age { get; set; }
    public float Weight { get; set; }
}

これをUI Bulderで表示すると以下のようになります。

左下の Library Project に変更すると、C#で定義したカスタム要素がリストされます。そしてそれをドラッグ&ドロップでViewportに配置し、選択したのが上図です。

C#側で定義したプロパティがインスペクタに表示されているのが確認できます。このように、UI Builderに配置するためのパーツも定義することができます。場合によってはPrefab以上の効率化が見込めるでしょう。

今回は詳細なコード解説は割愛しますが、大事なポイントに絞って列挙します。

  • カスタム要素は VisualElement クラスを継承する必要がある
  • 継承先クラスの内部クラスで UxmlFactory クラスを上書き( new )する
  • 必要なプロパティを Uxml****AttributeDescription クラスを使って定義する
  • 上記プロパティ定義に沿ったC#のプロパティを定義する

以上の対応を行うことでUI Builderに表示され利用することが可能となります。

UI ToolKitにおける空要素はVisualElement

VisualElement を継承してカスタム要素を作ると解説しましたが、UI ToolKitにおける空要素は VisualElement になります。そのため独自で定義したカスタム要素にもスタイルを適用することができます。

実際にスタイルを適用してみると以下のようになります。

しっかりとスタイルが適用されていることが確認できます。

ボタンのイベントなどを扱う

デメリットのところで、ボタンなどを SerializeField に設定してのセットアップができないと書きました。ではUI ToolKitではどう処理をしたらいいのでしょうか。その方法について見ていきましょう。

UXMLで定義された要素へのアクセスは UQuery と呼ばれる拡張メソッドによって提供される機能を使います。UQueryはドキュメントによると以下のように説明されており、Web開発で利用されるjQueryやLinqを参考に作られているようです。

> UQuery is based on JQuery or Linq

クエリと名前がつく通り、取得したい要素を指定して取り出し、該当要素に対して処理を施すというのが大まかな流れになります。ここではボタンのイベントをトラッキングし、実際に処理を行う様子を見てみましょう。

[SerializeField] private UIDocument _uiDocument;

// --------------

private void Start()
{
    Button button = _uiDocument.rootVisualElement.Q<Button>();
}

UIDocument への参照を SerializeField で設定し、そのルート要素に対してクエリを発行することで該当のオブジェクトを取得することができます。また、以下のように名前を指名して取得することも可能です。

Button button = _uiDocument.rootVisualElement.Q<Button>("button-name");

ここで取得される Button はuGUIの UnityEngine.UI.Button ではなく、UnityEngine.UIElements.Button となります。この Button には clickable というプロパティがあるので、これを利用してイベント購読を行います。

button.clickable.clicked += () =>
{
    Debug.Log("Clicked a button!");
};

前述したように、UXML内の要素を SerializeField で指定することができないので常にルート要素からクエリをかける必要があります。そのため、UI Document を分けるなどして構造化し、それぞれにスクリプトをアタッチするというような対応が必要になるでしょう。

uGUIとの共存

uGUIとUI ToolKitは共存させることができます。ゲームビューにどちらも表示することができ、レンダリング順(どちらが上に描画されるか)も制御することができます。

以下のふたつの画像を見てください。UI ToolKitとuGUIを同時に表示した例です。前者はuGUIが後ろに描かれ、後者は前に描かれています。

uGUIのUIがUI ToolKitのUIの後ろに隠れている


uGUIのUIがUI ToolKitのUIの前面に表示されている

これを実現するためには Canvas Sort Order の値を変更する必要があります。

Render ModeWorld Space にすると Sort Order を変更しても前面に描画されなくなるので注意してください。

Sort Orderの値を、デフォルトの 0 から 1 に変更

RenderTextureに描画して使う

上で少し触れたように、UI ToolKitのUI描画のターゲットをゲームビューではなく RenderTexture にすることができます。UIすべてを描画して利用してもいいですが、一部だけ利用するという形も考えられます。

例えばuGUIでは苦手なテクスチャレスな角丸ボーダーのUIなどを描画する、といった具合です。こうした部分は背景になることが多いと思うので、この背景画像をUI ToolKitのパワフルな描画エンジンを用いてテクスチャ化し、それをuGUIの背景に設定する、という使い方もありえるでしょう。

RenderTextureをTarget Textureに設定する

Panel Settingsの設定項目を見ると Target Texture があります。これは RenderTexture を設定する項目になっており、これが設定されているとゲームビューではなく、設定された RenderTexture にUIが描画されます。

これを応用して、UI ToolKitのUI描画をレンダーテクスチャに対して行い、そのレンダーテクスチャをuGUIの RawImage に貼り付ければUI ToolKitで描画したものをuGUIで利用することが可能になります。

最後に

ここまで解説してきたように、現時点でもかなりパワフルにUIを構築することができます。uGUIでは構築が面倒だったものもUI ToolKitなら手軽に構築できるだけでなく、テキストベースであるため管理や共同作業がしやすいのもメリットでしょう。

また後半でuGUIとの共存についても書きましたが、uGUIを使うかUI ToolKitを使うかゼロ・イチの話ではなく、適材適所で使い分けることも可能です。UI構築において手段を複数持つことは、プロジェクトで抱えている課題解決にとても有用です。ぜひこの機会にUI ToolKitを導入してみてはいかがでしょうか。

--

筆者紹介:えど

ARスタートアップのMESONでUnityエンジニアとして従事。もとはカヤック時代にWebエンジニアとしてリーダーを務め、その後VRと出会いコロプラに転職。コロプラでは仮想現実チームにてXRコンテンツ開発に携わる。Daydream向けゲーム「Nyoro The Sanke & Seven islands」をリリース。またプライベートでもAR/VRの開発をしており、インディー部門でTGSに出展など公私関わらずAR/VRコンテンツ制作に精を出す。プライベートな時間でも開発しているように、新しいことを学ぶ事が趣味で、最近は英語を学んでいる。

Twitter:https://twitter.com/edo_m18
この記事はいかがでしたか?