(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

(:3[kanのメモ帳]

uGUIの負荷を減らす、7つの最適化【Unity】【uGUI】【最適化】


このエントリーをはてなブックマークに追加

この記事でのバージョン
Unity 2018.4.6f1


はじめに

公式でUnityのお役立ち情報を配信しているUnity for Proというサイトがあるのですが、

(ライセンスのProではなく、実務で使えるという意味でのPro)

f:id:kan_kikuchi:20190914061149j:plain


その中の「Unity UI の最適化に関するヒント」といういかにも重要そうな記事があったので、



今回は勉強がてら、この記事を自分なりにまとめ直してみました!


1. 頻繁に動くものはCanvasを分ける

Canvasは要素(子に含まれるもの)の中に1つでも変更があると、Canvas全体、

つまり全ての要素の描画のための計算をやり直します。

Canvas の要素に 1 つでも変更があると、変更された要素の描画を最適化するために、Canvas 全体を再分析する

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


逆に言うと変更さえなければ、計算が起こらないため軽いという事でもあるので、

もし頻繁に動くものがあればCanvasを分ける方が良いでしょう。

f:id:kan_kikuchi:20190915045531g:plain


ちなみに、以前この比較記事を書いてるので実際の数値が気になる場合は、参考になるかと思います。

f:id:kan_kikuchi:20190203141025j:plain




2. 無駄なRaycastをなくす

Canvasにデフォルトで付いてるGraphic Raycasterですが、

これはタッチやクリックを判定するために必要なものです。

f:id:kan_kikuchi:20190914063657j:plain


もちろんタッチやクリックの必要がないUIの場合でも、判定は行われてしまうので、

そういう時は無駄な処理が行われないようGraphic Raycasterを削除しましよう。


また、ImageやTextなどに付いてる「タッチやクリックが出来るか」を設定するRaycast Targetも同様で、

f:id:kan_kikuchi:20190914063821j:plain


タッチやクリックの判定が必要ない時は無効にすると無駄な負荷が減ります。

(デフォルトでは有効になってるので、注意が必要)


3. Camera.mainに何度もアクセスしない

Camera.mainを使う事で、MainCameraというTagが付いてるカメラ

(最初からシーンに配置されてるカメラ)を簡単に取得する事が出来ますが、

f:id:kan_kikuchi:20190915045001j:plain


実はCamera.mainはアクセスするたびにタグで検索してるので、重い処理だったりします。

_mainCamera = Camera.main;
//↓と同じような事をしている
_mainCamera = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<Camera>();


AwakeやStartなどで1度だけ取得する分には問題ありませんが、

Updateなどで何度もアクセスするのは避けましょう。


uGUIの話に戻ると、CanvasのRender ModeをWorld Spaceにすると

Event Cameraという項目が表示され、このデフォルトがNoneなのですが、

なんとNoneの状態だとCamera.mainにアクセスします。

f:id:kan_kikuchi:20190915045406j:plain


しかも 1 フレームあたり 7 〜 10 回もアクセスするという事なので、Noneのまま使うのは避けましょう。


4. Layout Group(Scroll Rect)を使わない

TextやImageなどの要素を等間隔に並べたい時に便利なLayout Groupというコンポーネントがあります。

f:id:kan_kikuchi:20190915050036j:plain


このLayout Group、なんと使うとGetComponentsが実行される回数が増えて重くなるので、

使わない方が良いらしいです。

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

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


もし、等間隔に並べたい場合はそういうコードを自分で書き、

それを適宜(要素が追加された時など)実行するようにすると良いでしょう。


ちなみにスクロールの実装に使うScroll RectもLayout Groupなので、これも使わない方が良いようです。





5. オブジェクトプール時の処理の順番に気を付ける

オブジェクトを削除(Destroy)したり、新しく作ったり(Instantiate)する処理は重いので、

何度も使うオブジェクトを再利用するために、

使い終わったものを一旦非表示(gameObject.SetActive(false))にして置いておき、

また使う時に表示するというオブジェクトプールという方法があります。


そのオブジェクトプールをuGUIで使う際に気を付ける事があります。

例えば以下のように通常のCanvasと、オブジェクトプール用のCanvasが違う場合、

f:id:kan_kikuchi:20190915053710j:plain


使わなくなったものは非表示にしてからCanvasを移動させた方が良いです。

//使わなくなったものを非表示にしてから、
_text.gameObject.SetActive(false);

//Canvasを移動
_text.transform.SetParent(_objectPoolCanvas.transform);


こうすることで、変化があったのはCanvasだけになり、

ObjectPoolCanvasでは変更処理が走らないので、負荷が減らせます。

(移動してから非表示にすると両方に変更処理が走る)


同様に、再度表示する場合は移動してから表示するようにします。

//ObjectPoolCanvasからCanvasに移動してから、
_text.transform.SetParent(_canvas.transform);

//表示
_text.gameObject.SetActive(true);



6. Canvasを非表示にする時はコンポーネントを無効にする

Canvasを非表示にする際、オブジェクトごと非表示にすると

_canvas.gameObject.SetActive(false);
f:id:kan_kikuchi:20190916053332j:plain


再度表示する際に再構築(どういう描画をするかの計算)が発生してしまいます。

さらにOnDisableやOnEnableも発生するので、余計に処理が重くなります。


しかし、Canvasのコンポーネントを無効にして非表示にすれば、

_canvas.enabled = false;
f:id:kan_kikuchi:20190916053349j:plain


再構築やOnDisable、OnEnableも発生しないので、

Canvasを非表示にする時はコンポーネントを無効にしましょう。


7. 変更がないAnimator(Animation)は使わない

Animator(Animation)はuGUIでも使えますが、

何も変更がないアニメーションだったとしても、実行中は常にCanvasの再構築が行われています。

f:id:kan_kikuchi:20190915055238g:plain


なので、Idle的な何も変化がないアニメーションはuGUIでは使わず、

Animatorを無効にするようにしましょう。


もしくはAnimator自体を使わず、

DoTweenなどのTween系アセットで動かしたい時だけ動かすというのもありです。





おわりに

uGUIは楽に実装出来る反面、間違った使い方をすると簡単に負荷が増えてしまうので、

今回のような事を知っておくのはかなり大事だったりします。


また、Layout Groupのように標準機能が罠ということが

uGUIにかかわらずUnityには結構あるので、これも大事な話です。