Search Unity

Unity UI の最適化に関するヒント

  • 開発者向け

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

Canvas を分割する、Camera.main やレイアウトグループの使用を避ける、UI オブジェクトをスマートなやり方でプールするなど、コンテンツに合わせて最適化された UI 要素を作成する方法のヒントをご紹介いたします。 Unity のエンジニア Ian Dundore による素晴らしいセッション「Squeezing Unity: Tips for raising performance」(Unity UI のセクションは 23:38 から始まります、Youtubeの自動翻訳機能で日本語を選ぶことができます)を視聴して、より一層役に立つ情報を見いだしましょう。

目次

1)Canvas を分ける

問題:UI Canvas で要素が変更されると、Canvas 全体を再分析する処理が走ってしまう

Canvas は Unity UI の基本的なコンポーネントです。Canvas に配置された UI 要素を表現するメッシュを生成し、UI 要素が変更された場合はメッシュを再生成します。そして、UI が実際に表示されるように GPU に対してドローコールを発行します。

このようなメッシュの生成は、コストが高くなる場合があります。そのため、UI 要素はバッチに集めて、できるだけ少ないドローコールで描画できるようにする必要があります。バッチの生成はコストが高いため、再生成はできるだけ少ない回数に抑え、必要な場合にだけ行うようにしなければなりません。問題は、Canvas の要素に 1 つでも変更があると、変更された要素の描画を最適化するために、Canvas 全体を再分析するところです。

単一の Canvas に、数千の要素からなるゲーム全体の UI を構築してしまうユーザーがよくいます。このような状況では、1 つの要素を変更しただけで CPU のスパイクが発生し、処理が数ミリ秒ブロックされてしまうことがあります。再構築のコストが高い理由は、Ian の講演動画の 24:55 付近で詳しく解説されています。

解決法 : Canvas を分ける

各 Canvas で描かれている要素は、別の Canvas 上から分離した要素の集まりです。そこで、バッチ処理の問題を解決できる Unity UI の主要ツールで Canvas をスライスします。

また、Canvas をネスト化することもできます。これにより、デザイナーは多くの Canvas にまたがり画面上のさまざまな要素がどこにあるのかを考えることなく、大きなヒエラルキーを持つ UI を作成できます。子 Canvas は、親 Canvas と兄弟 Canvas の両方からコンテンツを分離します。それらは独自のジオメトリを維持し、独自のバッチ処理を実行します。

Canvas の要素を子 Canvas に分離するときは、更新されるタイミングに基づいて要素をグループ化するとよいでしょう。たとえば、動的要素と静的要素を分離するという方法があります。Canvas のスマートな分離方法の一例は、Ian の講演動画の 29:36 付近で解説されています。

2)Graphic Raycaster の最適活用

問題:Graphic Raycaster の最適な利用

Graphic Raycaster は入力をUI のEvent に変換するコンポーネントで、画面 / タッチ入力をイベントに変換し、操作する UI 要素に送信します。なお、入力が必要な Canvas は、Sub-canvas を含め、すべてに Graphic Raycaster が必要です。

その名前にもかかわらず、Graphic Raycaster は実際にはレイキャスターではありません。デフォルトでは、UI グラフィックスのみをテストし、指定された Canvas 上で入力を受け取る UI 要素のセットに対して、レイの交差チェックを行います。そして、Graphic Raycaster の Canvas 上にある各 UI 要素の RectTransform に対して、入力イベントの発生するポイントが Interactive としてマークされているかどうかを確認します。

問題は、すべての UI 要素が更新を受け取るわけではないということです。

解決法:静的または Interactive 指定していない要素の Raycast Target を外す

たとえば、ボタンの上に乗るテキストです。Raycast Target を外せば、Graphic Raycaster が毎フレーム必ず実行する交差チェックの回数を減らすことができます。

Unity UI の最適化に関するヒント

問題:特定の場面では Graphic Raycaster がレイキャスターとして機能する

Canvas の Render Mode を Worldspace Camera または Screen Space Camera モードに設定した場合は、ブロックするマスクを設定することもできます。ブロックするマスクは、Raycaster が 2D か 3D の物理演算を介してレイをキャストするかどうかを判断し、物理演算を適用したオブジェクトによって、UI に対するユーザー操作をブロックするかどうかが決定されます。

解決法:2D / 3D の物理演算を介したレイキャスティングはコストが高いので、使用を控える

Interactive に設定していない UI Canvas には Graphic Raycaster を追加せず、Graphic Raycaster の数を最小限に抑えましょう。なぜなら、Interactive に設定していない UI Canvas でインタラクションイベントをチェックする必要はまったくないからです。

3)Camera.main の使用を避ける

問題:World Space の Canvas から操作イベントを取得する元のカメラを知らせる必要がある

World Space またはカメラのスクリーンスペース内にレンダリングするように Canvas を設定する場合、UI の Graphic Raycaster のインタラクションイベント生成に使われる Camera を指定できます。この設定は「Render Camera」と呼ばれ、「Screen Space - Camera」の Canvas に必要です。

Unity UI 最適化のヒント:Screen Space - Camera

一方「World Space」では、この Camera は「Event Camera」と呼ばれ設定は必須でありません。

Unity UI 最適化のヒント:World Space

World Space の Canvas で Event Camera を空白にしていても、Canvas は Event を受け取りますが、その場合はゲームのメインカメラを使用します。その際、どのカメラがメインカメラなのかを判断するために、Camera.main プロパティにアクセスします。

Unity UI 最適化のヒント:Camera.main プロパティー

Unity の取るコードパスによりますが、各 Graphic Raycaster は World Space の Canvas ごとに 1 フレームあたり 7 〜 10 回 Camera.main にアクセスします。Camera.main はアクセスされるたびに Object.FindObjectWithTag を呼び出します。この動作が、実行時に悪影響を及ぼすことは明らかです。

解決法:Camera.main の使用を避ける

カメラへの参照をキャッシュし、メインカメラを追跡するシステムを作成しましょう。また、World Space の Canvas を使用する場合は、常に Event Camera を指定して、この設定を空欄にしないようにしましょう。Event Camera を変更する場合は、Event Camera のプロパティを更新するコードを記述してください。

4)可能な限り Layout Group を回避する

問題:Layout をダーティにする UI 要素が、最低でも GetComponents を 1 回以上呼び出してしまう

レイアウトシステム上で複数の子要素が変更されると、そのレイアウトシステムはダーティになります。また、変更された子要素は、その要素が所属するレイアウトシステムを無効にします。

レイアウトシステムについて:レイアウトシステムは、レイアウト要素のすぐ上にある一連の連続したレイアウトグループです。レイアウト要素を構成しているのは、コンポーネントだけではありません。UI イメージ、テキスト、および Scroll Rect もレイアウト要素に含まれます。また、Scroll Rect はレイアウトグループでもあります。

問題の振り返り : 自身のレイアウトをダーティとしてマークする UI 要素は、少なくとも GetComponents を 1 つ以上呼び出します。この呼び出しは、まずレイアウト要素から、親の有効なレイアウトグループを検索します。該当するレイアウトグループが見つかった場合は、検索を停止するか、ヒエラルキーのルート階層に到達するかのいずれか早い方で、ヒエラルキー内の Transform を上っていきます。したがって、各レイアウトグループは、それぞれの子レイアウト要素のダーティ化処理に GetComponents の呼び出しを 1 回追加することになり、ネスト化されたレイアウトグループのパフォーマンスを極度に悪化させます。

解決法:可能な限り Layout Group を避ける

プロポーショナルレイアウトを設計するときは、アンカーを使用します。なお、要素数が動的に変化する UI に関しては、レイアウトを計算する独自のコードを記述し、すべての変更に対してそのコードは使わず、オンデマンドでのみ使用するようにしてください。

5)賢いやり方で UI オブジェクトをプールする

問題:間違ったやり方による UI オブジェクトのプール

UI オブジェクトの親を再設定してから無効にしてプールする手法がしばしば使われますが、これは Canvas を不必要にダーティにする原因となります。

解決法:まずオブジェクトを無効にして、親をオブジェクトプールに切り替える

このやり方は、古いヒエラルキーを一度ダーティにすることになりますが、親を再設定すると古いヒエラルキーを再びダーティにしたり、新しいヒエラルキーをダーティにするのを回避できます。なお、オブジェクトプールからオブジェクトを削除する場合は、最初にオブジェクトを親を再設定してから、データを更新して有効にします。

6)Canvas を非表示にする方法

問題:Canvas を非表示にする方法

状況によっては、UI 要素や Canvas を非表示にしたい場合があります。これを最も効率的に行うには、どうすればいいでしょうか?

解決法:Canvas コンポーネント自体を無効にする

Canvas コンポーネントを無効にすると、Canvas はドローコールの命令を GPU に送らなくなり、Canvas が表示されなくなります。ただし、Canvas は頂点バッファーを破棄しません。 すべてのメッシュと頂点を保持しているため、Canvas コンポーネントを有効に戻せば、再構築はせずに再描画を開始します。

さらに、Canvas コンポーネントを無効にしても、Canvas のヒエラルキーでコストのかかる OnDisable / OnEnable のコールバックは発生しません。そのため、コストのかかるフレームごとのコードを実行する子コンポーネントを無効にするように気をつけましょう。

7)UI 要素上でアニメーターを最適利用する

問題:UI でアニメーターを使う

たとえアニメーションの値が変わらなくても、アニメーターはそのフレームごとに要素をダーティにします。アニメーターにはノーオペレーションのチェックはありません。

解決法 :

常時変化する動的な要素にのみアニメーターを配置するようにしましょう。ほとんど変化しない要素や、短時間のイベントのみに反応して変化する要素については、自作のコードを記述するか、トゥイーンの設定で独自にコードを記述するようにします(アセットストアに多数あります)。

その他のリソース

この記事はいかがでしたか?