Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

【Unity】UI ToolkitをランタイムUIとして使ってみる

この記事はUnity Advent Calendar 2020の12月16日の記事です。

Webフロント技術からインスパイアされて開発されたUnityの次世代UIシステム「UI Toolkit」がUnity2020.1からランタイムUIサポートをしています。1
今までのGameObjectベースではなくHTML/CSSのような記述で開発します。

UI Toolkit is based on, and inspired by, standard web technologies. If you have experience developing web pages or applications, much of your knowledge might be transferable, and many of the core concepts might be familiar.

Unity - Manual: UI Toolkit参照

ここ最近はずっとuGUIで開発していた自分としてはとても気になっていたため、UI ToolkitをランタイムUIとして使ってみることにしました。

執筆する上で作ったもの

移動・透明度のアニメーションをしながら表示して、ボタンをクリックしたら全体が消えていくシンプルなUIを作ってみました。

complete3.gif

本記事では大きく以下の2点について説明していきます。

  1. UI Toolkitを使ったUI画面構築方法
  2. アニメーションの実装方法

UI Toolkit全くやったこと無い人が何となく理解できるようになるというのが目標とします。

環境

  • Unity2020.2.0b14.3668
  • UI Toolkit 1.0.0-preview.13

1. UI Toolkitを使ったUI画面構築方法


この画面をUI Toolkitで作っていく過程を説明していきます。

  • 3隅にそれぞれLabel
  • 真ん中に画像
  • 真ん中下にButtonとLabel

このような構成です。
※背景は3D空間上に配置しているためUI Toolkitで管理していません

旧来のuGUIのように要素ごとにGameObjectとコンポーネントををHierarchyにペタペタ配置していくということはせず、UXMLと呼ばれるXMLのようなフォーマットで書いていきます。

先のUI構成要素をUXMLで表現すると以下のようになります。
※Sample.uxmlというUXMLファイルを作成しています。

<ui:UXML xmlns:ui="UnityEngine.UIElements">
    <!-- 左上 : バージョン表記 -->
    <ui:Label text="ver1.0.0-preview" name="Version" />
    <!-- 右上 : ユーザーID表記 -->
    <ui:Label text="UserId : 10378426348" />

    <!-- 中央UIパーツ -->
    <ui:VisualElement name="LogoContainer">
        <!-- ロゴ画像 -->
        <ui:VisualElement name="ProductLogo" />
        <!-- ボタン -->
        <ui:Button text="TAP TO START&#10;" name="BtnTapToStart" />
    </ui:VisualElement>

    <!-- 左下 : コピーライト表記 -->
    <ui:Label text="2020(C)ohbashunsuke" />
</ui:UXML>

適宜説明をコメントアウトしているので深く説明しませんが、HTMLでマークアップしていく感覚でUI要素羅列していきます。

UXMLの簡易説明

ソースコードに3つの要素が出てきました。

  • VisualElement
  • Label
  • Button

LabelButtonVisualElementは、それぞれUnityEngine.UIElements内のクラス名です。

<ui:Label text="ver1.0.0-preview" name="Version" />

例えばこれは「Labelクラスのインスタンスにver1.0.0-previewという文字列をtextプロパティに代入した」という意味になります。

ちなみに現状のUXMLを画面に映し出してみます。

UXMLを画面に反映する準備

Create > UI Toolkit > Panel Settings Assetを選択してProjectにPanelSettingsを作成しておきます。

  1. ヒエラルキーにGameObjectを追加(UIGameObjectと命名)
  2. UIGameObjectにUIDocumentコンポーネントをAddComponent
  3. UIDocumentのインスペクタのSourceAssetに先のUXMLをセット
  4. 予め作成したPanelSettingsUIDocumentインスペクタのPanel Settingsにセット


このようなインスペクタになります。

画面に表示!!


UI要素をUXMLに記述しただけなので、書いた順に要素が表示されています。

ここから配置、装飾処理を施していきます。

USSファイルで配置と装飾

.
├── Sample.uss
└── Sample.uxml

Sample.ussをSample.uxmlと同階層に作成します。
USSファイルはHTML/CSSでいうCSSにあたります。

USSをUXMLにロードさせる

<Style src="Sample.uss" />

上記のコードをUXMLの2行目に追加します。
これでSample.uxmlがSample.ussを参照するようになります。

スタイルの定義

まずは左上のテキストから。

/*左上要素*/
.lt {
    position: absolute;
    left: 14px;
    top: 14px;
}

position: absolute;absoluteを設定することで絶対座標で配置することを指示します。
left:14px, top:14pxの設定で左から14px、上端から14pxの位置に配置する事になります。

この設定をUXMLで指定します。

<ui:Label text="ver1.0.0-preview" name="Version" class="lt" />

バージョン表記部分に class="lt"を追加し、先のUSSで定義したltを適用しました。
※lt・・・LeftTopという意味

HTML/CSSの考え方と同じで、USSで定義したスタイルをUXMLの要素に割り当てます。この作業他2隅の要素に適用すると、以下のような見た目になります。
(ltの他rt・・・RightToplb・・・LeftBttomを追加)


各隅のテキスト要素が意図通り配置されました。

ソースコードを見ていきましょう。

Sample.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements">
    <!-- スタイルのロード -->
    <Style src="Sample.uss" />
    <!-- 左上 : バージョン表記 -->
    <ui:Label text="ver1.0.0-preview" name="Version" class="lt" />
    <!-- 右上 : ユーザーID表記 -->
    <ui:Label text="UserId : 10378426348" class="rt" />

<!-- ~~~~ ロゴ部分省略 ~~~~ -->

    <!-- 左下 : コピーライト表記 -->
    <ui:Label text="2020(C)ohbashunsuke" class="lb" />
</ui:UXML>

各Label要素にclassが追加されています。例えばclass="rt"が追加された要素は右上端から14pxの距離に配置されるという事になります。

USSファイルを見ていきます。

Sample.uss

/*左上要素*/
.lt {
    position: absolute;
    left: 14px;
    top: 14px;
}

/*右上要素*/
.rt {
    position: absolute;
    right: 14px;
    top: 14px;
}

/*左下要素*/
.lb {
    left: 14px;
    position: absolute;
    bottom: 14px;
}

USSには配置処理を記述しており、このclassが適用されるだけで配置処理が実行されます。

余談 : USSに変数を使用する

先のUSSファイル内に14pxがいくつも出てきました。変数に置き換えられると便利ですよね。
USSに変数を使用していきます。

--変数名: 値という文法で変数定義。var(--変数名)で変数を使用します。

サンプル

:root{
    --hogehoge:428;
}
.piyo{
    /* 「width:428;」と同じ処理になる */
    width: var(--hogehoge);
}

変数をどこに定義するのか?という話になりますが、公式リファレンスで:root要素に定義していたのでとりあえずそれに従っています。
Unity - Manual: USS custom properties (variables)

USSで変数を使うことで先のサンプルは以下のようになりました。

:root{
   /*変数margin定義*/
    --margin: 14px;
}

/*左上要素*/
.lt {
    position: absolute;
    left: var(--margin);
    top: var(--margin);
}

/*右上要素*/
.rt {
    position: absolute;
    right: var(--margin);
    top: var(--margin);
}

.lb {
    position: absolute;
    left: var(--margin);
    bottom: var(--margin);
}

画像の利用

#ProductLogo {
    background-image: url('/Assets/Project/Images/Logo.png');
    height: 258px;
    width: 604px;
}

画像をただ配置するのは簡単です。
background-image: url(画像のパス);widthheightを指定します。
widthheightの指定が無いとサイズ0という事なのか表示されません。

このように画像も配置されました。

最後にボタン

角丸ボタンを作りたかったので、9スライスでの使用を想定した画像を使用しています。
button-base-0.png

#BtnTapToStart {
    top: auto;
    width: 302px;
    height: 80px;
    position: absolute;
    bottom: 18%;
    font-size: 26px;
    opacity: 1;
    background-image: url('/Assets/Project/Images/Atlas/OutGame/button-base-0.png');
    -unity-background-scale-mode: stretch-to-fill;
    -unity-slice-left: 10;
    -unity-slice-top: 10;
    -unity-slice-right: 10;
    -unity-slice-bottom: 10;
    background-color: rgba(0, 0, 0, 0);
    color: rgb(210, 210, 210);
}

画像のスライス処理が入っているので、多少コードが長くなっています。

ボタンクリック時の処理をC#側に書いていきます。

var document = GetComponent<UIDocument>();
var root = document.rootVisualElement;
// 「BtnTapToStart」をキーにしてボタンインスタンスを取得
var tapToStartButton = root.Q<Button>("BtnTapToStart");
tapToStartButton.clicked += () => Debug.Log("画面全体の透明アニメーション処理実行");

指定要素の名前で取得する場合はQ<T>(要素名)を使うと良いでしょう。


このような感じでUI要素がすべて表示されました。

UI Toolkitにおける画面の構築方法のまとめ

  • HTML/CSSのような感覚で配置とスタイルを適用
  • UIDocumentコンポーネントでUXMLを画面に表示
  • USSで変数を使う場合、--変数名: 値で定義、var(--変数名)で使用
  • Q<T>(キー)で要素を取得

2.アニメーションの実装方法

UI要素の配置とデザインの反映は出来ました。
ここからはアニメーションの実装方法を検証していきます。

スクリプトでアニメーション

大好きなDOTweenを使ってアニメーションをさせてみます。
DOTween (HOTween v2) | アニメーション ツール | Unity Asset Store

平行移動アニメーション

slidex.gif
ヘッダ・フッターの横スライドアニメーションを実装していきます。2

var root = GetComponent<UIDocument>().rootVisualElement;
VisualElement target = root.Q<VisualElement>("target");
// 2秒かけて(-100, 0, 0)に向かってtargetを移動させる
DOTween.To(() => target.transform.position,
    x => target.transform.position = x, new Vector3(-100, 0), 2f)
    .SetEase(Ease.OutQuart);

VisualElementにはtransformというITransform型のプロパティが定義されています。以下がITransform型のソースです。

namespace UnityEngine.UIElements
{
  public interface ITransform
  {
    Vector3 position { get; set; }
    Quaternion rotation { get; set; }
    Vector3 scale { get; set; }
    Matrix4x4 matrix { get; }
  }
}

このようにTransformコンポーネントと同じようなプロパティが定義されているため、 VisualElementでも同様の使い方でアニメーションさせることが出来ます。

パーツの初期座標

各パーツVisualElementの実行時の座標ITransform.positionは配置された場所が(0, 0, 0)です。

透明度アニメーション

fade2.gif

ロゴ部分の透明度アニメーションを実装していきます。

void Alpha(VisualElement ve, float duration, float alphaValue)
{
    DOTween.To(() => ve.resolvedStyle.opacity,
            x => style.opacity = new StyleFloat(x), alphaValue, duration);
}

透明をアニメーションさせる場合は、このようなコードになります。
ポイントとしては、以下の現在の透明度を取得部分です。

// 現在の透明度
var currentOpacity = ve.resolvedStyle.opacity;

VisualElementクラスのresolveStyleプロパティから透明度(opacity)を取得する必要があります。1

AnimationClipでアニメーション実装

デフォルト状態では無理(だと思う)。

AnimationClipはシリアライズされるものしか扱えません。
UXML内のオブジェクトにAnimationClipがアクセスすることが出来ないため、工夫が必要になりそうです。少なくともデフォルト状態では使えません。

UI Toolkitのアニメーション実装のまとめ

  • スクリプト実装一択(デフォルト状態ではAnimationClipは使えません)
  • UIパーツの配置座標はそれぞれ(0, 0, 0)
  • 現在の透明度はresolveStyleから取得する
  • DOTween便利

まとめ

本記事ではUI ToolkitをランタイムのUIとして使ってみました。
uGUIと違ってUIパーツの数分GameObjectを作らなくてよいのは新鮮でした。

USSでUIパーツの配置・装飾を定義したら、それを反映するだけで全体の見た目を一括で更新できてしまうのは、UIレギュレーションを守る機能として強力だなとも思いました。
(※その分設計が命ということになるののですが...)

今回はあえてUI Toolkitのヘルパー機能UI Builderには触れていません。実際にUIToolkitでUIを構築する際はUI Builderを使った方が良いです。こちらはまたの機会に。

  • デザイナー「UXMLで構成要素を定義していく、配置や装飾はUSSで記述していく」
  • エンジニア「C#で各要素の処理を書いていく、アニメーションを付けていく」

このような分業が理想ではあるし、UI Toolkitで出来るような気もしますが、ただ現状の機能ではまだまだ課題が多い気もします。僕自身の検証も足りていないため、なんとも言えません。

以下パッと思いついた課題を羅列しています。未解決です。

思いつく課題

  • GameObjectで作成するより自由度が低い
  • ParticleSystemをUIで挟む構造は現状出来そうにない
  • UIにポストエフェクトをかけられるか?=>RenderTextureに書き込んでuGUIのRawImageで表示する?
  • USSファイルのインポート、使い回し方法
  • 複雑なボタンの作り方
  • ボタンの長押し対応
  • USSからSpriteを利用する方法はある?
  • AnimationClipが使えないためデザイナーがUIアニメーションを付けれない?(ソースコードを編集するのはハードル高し)

などなど。

この辺りは引き続き検証していく事になりそうで、まだまだ実戦投入するには早い印象です。
ただHTML/CSS感覚でUnityのUIを作れるのは面白いため試していない方は一度体験してみると良いかと。

今回のサンプルプロジェクトをGithubにアップロードしていますので気になる方はどうぞ。
baobao/UIToolkitRuntimeUISampler
※本記事の内容は説明のため簡略化していますのでリポジトリ内のファイルとは差分があります

最後に

Unityを使ったUI開発について今年のアドベントカレンダーに投稿しています。そちらもよろしくお願い致します。
【Unity】新規ゲームのUI開発で気をつけた39のTips前編 - Qiita

明日は@glitchpopさんの番です。

参考リンク


  1. 今までEditorのスクリプトのみ対応していました 

  2. 紹介しているgif動画は透明+移動アニメーションですが、脳内補完をお願い致します。 

ohbashunsuke
Twitterで初学者にUnityのチーム開発ノウハウを分かりやすくお届け。元デザイナー経験を生かしクリエーターと連携したエンジニアリング情報を共有しています。 🏢 株式会社サイバーエージェント ゲーム部門所属
https://www.shibuya24.info/
engineerlife
技術力をベースに人生を謳歌する人たちのコミュニティです。
https://community.camp-fire.jp/projects/view/280040
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away