Unityでインディゲームを作る!

Unityでのゲーム制作を目指し、それに関わる話題についてのブログ

Unity 6.7で追加される、UI Toolkit新機能を震えて待て!

現在、Unity 6.7のベータ版を触っていますが、ここで追加されるUI Toolkitの新機能はかなり盛りだくさんになっています。6.7の正式版はまだ先だと思いますが、期待して待って良いです!実験段階のモノも多いですが、いよいよUI Toolkitが完成しつつあります!

 

 今回はそんなUI Toolkitの新機能の個人的に気になったいくつかを簡単に紹介していきたいと思います。

 

TextElementにSetTextメソッドが追加!

※Unity 6.6でも使えるようになっていました。

 uGUIつまり、TextMeshProにおいてはTMPro_TextにSetTextメソッドがあり、UI Toolkitにも追加されないかなと思っていたのですが、6.7にてとうとう来ました!

 TextElementを継承するラベル(Label)で使うことが出来ます。いわゆる文字列を新たにアロケーションすることなく、テキストデータを設定するメソッドで、ReadOnlySpan<char>をそのまま入力することが可能です。

UITK_SetText

 なので、ScriptableObjectに持たせたテキストデータをAsSpanメソッドでReadOnlySpanに変換し、オーバーヘッド無しに表示することができるようになるということです。個人的にはこれが一番うれしい新機能かもしれません!

 

UI構造をヒエラルキー上で

UITK_VisualTreeInHierarchy

 ヒエラルキー上でVisualTree構造が表示されるようになり、それに伴いエディタのインスペクタから直接UI要素の編集が出来るようになりました。Unityエディタと完全に統合されたということです。これにより、AnimationウィンドウでUIアニメーションを作成することもできるようになりました。(次項でUIアニメーションについて書きます)

UITK_InspectorForUITK

 

UIアニメーションが作成可能に

UITK_AnimationCreateMenu

 CreateメニューからUI Toolkit > UI Animation ClipでUIアニメーションのためのクリップを作成できます。これをUI要素のKeyframe AnimationのClipに設定して、Animationウィンドウを起動してアニメを作成します。

UITK_UIAnimationSettings

 上記の設定をした後で、ヒエラルキー上で指定のUI要素を選択すると、Animation上でレコーディング編集が出来ます。

UITK_UIAnimationWindow

 この例ではスプライトを切り替えていくことでUIアニメーションを実現しています。これも待望の機能ですね。

 

Z-indexの導入

 UXMLのヒエラルキーにおいて下にある要素ほどUIの前景にいる、というルールがありますが、このZ-indexによってこのヒエラルキー上の前後関係を無視して直接的にZ軸上の位置を設定できるようになりました。

 ヒエラルキーの上下関係で前後関係をコントロールするのは面倒でしたし、インタラクションによる前後関係の変化を簡単に付けられるのは良いと思います。

UITK_ZindexHoverSample

Window > UI Toolkit > Samplesに追加された新しいサンプルで、この機能の様々な使い方を確認することが出来ます。

UITK_ZindexSamples

 

Grid Layoutでグリッド状に並べる

 displayモードのNone, FlexにGridが追加されます。これによって、その要素の子要素を自動的にグリッド状に並べるようになります。通常、FlexDirection.Columnにボタンを追加していくと下画像のように縦に並べられていきます。

UITK_GridLayout_Flex

FlexDirection.Columnで縦に並べたボタン群が・・・

UITK_GridLayout_Grid

Gridモードにすることで、自動的に並べ替えられる

 これは多くの人が待ち望んでいた機能かもしれません。ベータ版ではまだ実験段階でありバグもいくつかありますが、完成が待ち遠しい新機能です。

 

Gapでアイテム間の距離を調整!

UITK_GapSection

 これはまさにGrid Layoutのための新パラメータのようなものですが、Spacingのmarginとpaddingに新たにGapが加えられました。

UITK_NoGap

 Gapを0にすると、前の項目にあったグリッド状に並べたボタン群はこのようになってしまいます。

 

VisualElementComponent

UITK_VisualElementComponent

 これは個人的に興味深い新機能ですが、[VisualElementComponent]アトリビュートを付けた構造体をUI要素にAddComponent出来るようになります。

Label label = new Label();

label.AddComponent(new TestVEComponent{ Counter = 1 });

 

 個人的にはこの機能をどう活かせるかの使い道はまだ思い浮かんでいないんですが、ランタイム中に臨機応変にUIを追加したりするような状況で使えるかもしれません。

 

背景にグラデーションを

UITK_GradientSettings

 background-imageにグラデーションを設定できるようになりました。これにより、画像を使わずに背景を良い感じにできます。線形と円形の2種類のモードが選べます。

UITK_GradientBackground

ラベルにはドロップシャドウを追加してます

 

複数のUI Builderを起動!

 これは6.7 b2時点ではまだちゃんと動いていない実験段階の新機能ですが、同時に複数のUI Builderを起動できるというものです。例えば、メインのUIとそこにテンプレート・コンテナとして読み込まれているUI Documentをそれぞれ立ち上げて並行して編集していく、ということが出来るようになります。これは早くちゃんと動くようにしてほしいですね!

 

まとめ

 というわけで、近づきつつあるUI Toolkitの完成にテンション上がったので今回こういう記事を書いてみました。Unity 6以降、UI Toolkitは順調に進化してきましたが、特に6.5以降は単純にその性能も向上し、新機能が追加され続けてきました。

 6.6での追加ですが、backdrop filterやdrop shadowも待望の新機能でした。個人的には6.7の正式版によってUI Toolkitはやっと一通りの基本機能が揃ったと思います。少し長かったですが、UI Toolkitを使ってきた良かったです。

 

UI Toolkitにおけるクラスの命名規約について

UI ToolkitではUSSというCSSをベースにした言語でUIのスタイリングを行いますが、そこでは主にクラスを使います。つまり、ピリオドから始まる・・・

.class-name

という風に定義される『クラス』をUI要素に設定して、そのUIの見た目をコントロールするということです。

 

 ここでの『クラス』とはHTML/CSSの文脈での『クラス』であり、当然C#の『クラス』とは異なるので注意してください。HTML/CSSクラス(以下、単にクラスと表記)はスタイリングだけでなく、UI要素の分類やグルーピングにも使えます。

 今回は、このクラスの命名規則がテーマです。基本的にはUnity公式が推奨している規約に沿うのが良いと思いますし、私もそれに従ってオリジナルのクラスを作成しています。

 

いわゆるケバブ・ケース

 まず基本としてピリオド'.'を頭において、続けてクラス名を書き、単語ごとにハイフンで分割するのがルールです。(肉を串で刺しているかのようなのでケバブ・ケース)

.player-name-label

 分かりやすいように、その要素名も含むことが多いと思います。

つまり・・・、

.info-submit-button

.player-member-listview

という感じですね。

 

BEMとは?

 Unity公式のe-bookで解説されていますが、UI ToolkitではBEM規則が推奨されています。これは上記のケバブケースを基本に、より詳細な情報を分かりやすくするための追加ルールです。

Block Element Modifier

(.block-name__element-name--modifier-name)

ということですが難しく考える必要はなく、つまりその要素の構造や付属的な情報(状態など)をクラス名の中で分割して表記するというもの。

アンダースコア'_'を二個並べたもの"__"でクラス名を分割し、そのUI構造を表現します。

.unity-base-slider__dragger

.unity-collection-view__item

 それぞれUnity公式によるSliderとListViewに使われている実際のクラス名です。Sliderには操作するためのUI要素(ドラッガー)がありますが、これはつまり、スライダー内にあるドラッガー(つまみ)なので、このように表記します。ListViewで表示される各アイテムは当然、ListViewに属するモノなので__でその構造を表現するということなのです。

 

 例えば、プレイヤーの装備画面におけるアイテムを表すカード内にある、名前を表示するラベルにクラス名を付けるとしたら・・・?

.equip-window__item-card__item-name-label

という感じになるでしょう。これはかなり厳密で冗長な命名ですが、UI構造を正確に表しています。(実際には.equip-item-card__name-labelくらいで良いかもしれません)

 

 そして次はハイフン2個"--"でそのクラスの状態や、疑似クラスでは表現できない追加情報を表します。これは疑似クラスではカバーできない状態を表現するものです。

.unity-collection-view__item--selected

 これはListViewにおいて選択されたアイテムに追加されるクラスで、これにより選択されたアイテムがどう表示されるかをスタイリングします。ゲーム中のショップで表示されるアイテムをプレイヤーが選択したとして・・・

.shop-item-card--selected

という風にクラス名を定義すれば、アイテム選択時に強調するようなスタイリングを設定できます。

unity-collection-view__item--drag-hover

これはListViewのアイテムをドラッグした状態で、ポインターを他のアイテム上に置くと追加されるクラスですが、まさに--drag-hoverでその状態を表しています。

 

 このようにBEMと言っても難しく考える必要はなく、クラス名が表している情報をより分かりやすいように、__や--で分割しようというものです。

 

まとめ

 というわけで、UI Toolkitにおけるクラスの命名規約について書いてみました。スクリプト上でのUIコントロールにおいて、Queryメソッドを使い一括して要素を取得する際にクラス名を使うことになるので、分かりやすく明確な命名が重要になります。そして、Unity側のクラス名と一貫性を持たせるためにも、このBEM規則に従った方が良いと思われます。

3Dゲームにおけるカメラ操作って無い方が良いのではないか?(そして何故TPS/FPSでは許されるのか)という話。

CameraControlFor3dGame_Cover

今回はゲームデザイン論というか、ゲーマーとしてもゲーム制作者としてもカメラ操作について薄々感じていたことを書きます。

 3Dゲームはかれこれ数十年やってきており、好きですし我ながら得意な方だと自負しております。しかし数年前から3Dゲームを遊んでいて、ふとした時にこう思うんですよね。『あれ?カメラ操作するのって面倒くさくね?』と。

 

カメラ操作は面倒?

 これはある意味で現代のビデオゲームを否定するようなことです。でも、四方八方に広がる巨大な三次元空間において、自分でカメラを動かさないといけないというのは実は結構な負担じゃないでしょうか?

 カメラに映り、そこから描画される画面による限定的な視野からしかプレイヤーはモノを見ることができないという制約がビデオゲームにはあるので、プレイヤーはカメラをグリグリと動かさなきゃいけない。(音による察知はある程度出来ますが)

 

 問題はこの『カメラを動かさなきゃいけない』というのが面白いのか?ということで、別に面白くはないですよね。それがゲームとしてどうなんだ、という話です。

 現実世界で我々は見たいモノがあれば、そちらに体をむけるなり、首を向けるなり、視線を傾けたりを基本ノーコスト、遅延無しで出来ますけど、パッドの場合わざわざボタンから親指を離して右スティックで意識的にカメラを動かさなきゃならない。

 

 だから、よっぽどカメラシステムやレベルデザインがしっかりしていないと、カメラ操作が単なる負担になってしまう。なんか無駄にカメラ動かしてる時間の方が長くね?ってゲームは誰もやりたくないわけで。そういう意味で、カメラ操作を無くせるなら無い方が良いんじゃないか?という今回の問題提起となります。

 例えば、ここ10年ぐらいの3Dゲームで言えば、スーパーマリオ・オデッセイくらいに良く出来ていないとカメラを自由に動かせる3Dゲームは厳しいんじゃないでしょうか。つまり、自分でカメラを操作する3Dアクションゲームを作るのはかなり難しいってことです。

 

TPS/FPSにおけるカメラ操作

 ここで今回、これに合わせて主張したいのが、じゃあ何故TPS/FPSは許されるのか?ということです。まさにTPS/FPSというシューターゲームはカメラを操作する3Dゲームに他なりません。なんならカメラ操作がメインのジャンルといってもいいです。しかし、ここ十数年のゲームシーンにおいて、TPS/FPSはメインジャンルであったわけで、今回のカメラ操作苦痛論の反証に成り得ます。カメラ操作が面倒なら、なぜそれがメインのジャンルが流行るのか?となりますよね?

 

 その答えとして自分が考えたのが、TPS/FPSは狙うゲームであり、つまりカメラについた照準を敵に合わせて撃つゲームで、カメラを操作することで最大リターン(つまりヘッドショット)を得られるゲームデザインだから、というものです。

 頭を撃ちぬけば敵キャラをほぼ一発で倒せるというのは、冷静に考えてみればけっこう安易で破格なリターンですよね。でも、それくらいのリターンが無いとプレイヤーにとってカメラ操作は単なる負担になってしまうんだと思います。

 漠然とした3D空間にプレイヤーを置いて、何がどこにあるのかも分からない状態で、カメラを操作させる、っていうデザインはもう無理なのかもしれません。なので、非シューターゲームでそういう問題をかなり解決したブレワイは、やはりすごかったんだなと思います。

 

というわけで、私の考えをまとめると・・・

3Dゲームにおいて、より現代的で没入感のあるゲーム体験にするには見下ろし型よりも、自由にカメラを動かせる方が良い→でも、漠然とカメラを操作し続けるのは楽しくはない、苦痛ですらある→カメラ操作に直接的で明確なリターンがあるTPS/FPSが流行る。

という感じのロジックですね。

 やはり改めて考えると、TPS/FPSの『画面の中央に敵キャラの顔を合わせてボタンを押せば一発で倒せる』という単純明快さは複雑化した現代のゲームにおいてかなり重要なデザインだったのだと思いますね。

 

3Dアクションゲームを作るということ

 で、ここからは私自身のゲーム制作にも関わってくるのですが、3Dアクションゲームを自分でちゃんと作るのはキツイぞっていうのが今回の記事から導き出されることでした。ここ十年くらい、純粋な3Dアクションゲーム(自分でカメラ操作するタイプの)があまり作られない、評価されないという傾向があったと私は思っているんですが、単純に作るのが難しいし、プレイヤーも今回書いた理由からこのジャンルをもはやあまり望まない、ということなのかもしれません。

 数十年3Dゲームを遊んできてる私ですら、単純なカメラ操作に疑問を感じるのですから、ゲーム初心者やライトユーザーにカメラを渡して3D自由に動かしてね!なんてことはかなりハードルの高いことだと思います。

 

 インディゲームにおいて、今回書いた問題を解決できている3D(アクション)ゲームはほとんどないと思います。カメラシステムだけでなく、レベルデザインも絡んできますから。

 なので、自分が今作っている3Dアクションゲームはプレイヤーにカメラ操作させないデザインにしております。自分でカメラ操作させるタイプの自由度の高い3Dアクションゲームは一旦諦めました(笑)

 

まとめ!

 というわけで、今後の3Dゲームにおいても重要なトピックであり続けるだろうカメラ操作問題について自分なりの考えをまとめてみました。カメラシステムとレベルデザインをよっぽど上手くやらなければ、カメラ操作はプレイヤーにとってただの負担でしかない、それに対してのジャンル的な解決策がTPS/FPSだった、というのが私の考えです。

 つまり、現代的なゲームが抱える構造的な問題に対して、それを中和できるゲームデザインを持っていたのがTPS/FPSだった、ということになります。これらは私の個人的な考えに過ぎませんが、この20年くらいのゲームジャンルのトレンドをそれらしく説明できる説なのではないかと思います。

 

UI Toolkit : UI DocumentコンポーネントをPanel Rendererに切り替える!

UI DocumentコンポーネントはUI Toolkitにおいて、UXMLファイル(UI Document)をシーン上に読み込み、ゲーム画面にUIを表示するという機能を持つものでした。

 しかし、UI DocumentというのはそもそもUXMLファイルそのものを指す言葉であるはずだし、命名が曖昧ということもあったのかPanel Rendererという同等の機能を持つコンポーネントが新たに追加されています。

UITK_PanelRenderer

 UI Documentというコンポーネント名は最善の命名ではなかったし、UIを実際に表示する機能はUI Toolkitにおける"Panel"が担っていたので、ちゃんと実態に合った名前になって良かったです。

 

 というわけで今回は、UI DocumentコンポーネントからPanel Rendererコンポーネントへの移行作業について書きたいと思います。

 

ワークフローの変更あり

 機能はほぼ同じ、つまりUI Builderなどで作成したUXMLファイルを読み込み、ゲーム画面に表示するというものですが、ワークフローには少し違いもあります。

UITK_PanelRenderer_Inspector

UIを初期化、コントロールするスクリプトを追加する

 おそらく、UI ToolkitおけるUI初期化をある程度Unity側で形式化したかったのか、UIReloadCallbackで行う形になりました。画像のようにPanelRendererを持つ、ゲームオブジェクトにUIコントロール用のスクリプトを追加します。

 

以前は・・・

_root = _uiDocument.rootVisualElement;

という形でUI Documentコンポーネントから直接的にルートのVisualElementにアクセスできたのですが、これがPanel Rendererでは出来なくなりました。この方が簡単だし直感的だったのになぁ、とは正直すこし思います。

 

 つまり、Panel Rendererへ移行するにはコードを書き直す必要がある、ということです。PanelRendererとVisualElementとintを引数に持つコールバック関数を書き、それをRegisterUIReloadcallbackとUnregisterUIReloadCallbackに登録します。

UITK_PanelRenderer_PlayerHUDscript

UITK_UIReloadCallback

 ここで引数のVisualElementは前述のUI DocumentのrootVisualElementに該当します。ここからVisual Treeへの参照を経て、各UI要素をQueryしていくというUI Toolkitにおけるお馴染みのワークフローとなります。

 

いつUI Reloadが起きるのか?

 私も移行したばかりなので、まだよくわかっていない部分があるのですが、UIReloadCallbackがいつ呼び出されるのか?というところで、ゲームを通して何度もリロードする必要のあるUIの場合、問題が生じるかもしれません。

 私の環境だと、例えばゲーム中に新しいシーンをシングル・モードでロードした時に、UIReloadCallbackが呼び出されないのか上手くUIの初期化が行われない、ということが起きたりしています。ここら辺は今後対応の仕方を考えたいです。

 というわけで、簡単にですが、新しいPanel Rendererコンポーネントについて簡単にまとめてみました。

 

Unity UI Toolkitにおける長さ設定 (UIElements.Length)

UI Toolkitにおいて、『長さ』はいくつかのモード(Auto, %, Pixel, None)で設定することが出来ますが、その際に使うことができる構造体がUIElements.Lengthです。

 

 AutoはUI ToolkitにおけるFlex-box レイアウトに従って自動的に長さが決められるモードであり、Flex ShrinkやFlex Growに基づいてその要素の長さが決定されます。

 

button.style.width = 10.0f;

 という風に直接数値(float)を入力する場合には、自動的にPixel(ピクセル)モードでの設定となりますが、Lengthを使えば%(パーセンテージ)で設定できます。

_button.style.width = Length.Percent(50.0f);

 %設定にすると画面や親要素の長さに応じた長さが設定されるので、様々な解像度の画面に対応する場合には、このLength.Percentを活用することになるかと思います。

UI Builder上での%による長さ設定

 

flex-positionをAbsoluteに設定したUI要素の位置を・・・

button.style.left = Length.Percent(10.0f);

button.style.bottom = Length.Percent(5.0f);

という風に自由に(Relativeモードのように他の要素の影響を受けないという意味で)直接的にポジションを%設定することも出来ます。

 

 もちろんハードによって(特にスマホ)アスペクト比が違うので、パーセントによる長さ設定は要素の見た目のバランスを変えてしまう可能性はあります。しかし、パーセント設定を上手く使えば、いわゆるレスポンシブなUIを作ることができるはずです。

 

 そして重要なのが、そのパーセンテージはどこを基準にしてのパーセンテージなのか?という点です。これはその要素を格納している親要素(いわゆるflex container)の長さを基準にしていると考えていいです。

 

 つまり、今見てるモニター画面のスクリーンそのものの大きさ、長さを参照しない場合があります。もし、スクリーンを二つのVisualElementで横に二分割していて、その中にボタンを置き、その横の長さ(width)を%設定した際に参照するのは、既に二分割された画面の横の長さ、ということです。

TestUI_Hierarchy

 下画像は画面を2つのVisual Elementで左右に分割し、左側にWidthを50%に設定したボタンを置いています。Visual Elementのwidthはそれぞれ50%に設定されており、その中の50%ということでボタンの長さは画面の4分の1となっています。

Width:50%Button

 この場合、ボタンの長さを100%に設定しても画面全体の半分の長さにしかならない、ということになります。

 

 こうしたUI Toolkitにおける、%設定の相互作用というのを理解して意識的にコントロールできるようになるには慣れと時間が必要ですが、使いこなせれば強力な機能となるはずです。

 

 アスペクト比が異なる状況でデザインに一貫性を持たせるアイディアについてはまた別の機会。ひとまず今回はUIElements.Lengthという構造体の紹介だけをさせていただきました!