設計からパフォーマンスを意識した開発（UI編）


この記事は、[Unity Advent Calendar 2017](https://qiita.com/advent-calendar/2017/unity) 16日目の記事です。

　
今年の夏コミで販売しまして [UNIBOOK8](https://booth.pm/ja/items/610817) で、第5章『初めてのパフォーマンスチューニング』ってのを書かせてもらいました。この内容から引き続き、パフォーマンス・最適化に関係するお話をしようと思います。



## Unity UI（uGUI）
パフォーマンスと言ってもUnityに関する技術は様々で、名前を挙げていくだけでも一苦労になるテーマになってきます。なので今回は、Unity UI（uGUI）関連に限定したいと思います。

また、Unity公式ページで公開されている [ベスト プラクティス ガイド](https://docs.unity3d.com/jp/current/Manual/BestPracticeGuides.html) の内容をベースに話を進めていきますので、疑問に思ったりもっと詳細を知りたい方は公式をご確認頂けると幸いです。


## Optimizing Unity UI
公式の各ベストプラクティスに関して、以下のような構成になっています。

* **[1. Unity UI の最適化ガイド](https://unity3d.com/jp/learn/tutorials/temas/best-practices/guide-optimizing-unity-ui?playlist=30089)**
    * まえふりとガイドラインについて
* **[2. Unity UI の基礎](https://unity3d.com/jp/learn/tutorials/topics/best-practices/fundamentals-unity-ui?playlist=30089)**
    * Unity UI上でどんな処理がおこなわれているのかや何が起こるのかなどを解説
* **[3. Unity UI のプロファイリングツール](https://unity3d.com/jp/learn/tutorials/temas/best-practices/unity-ui-profiling-tools?playlist=30089)**
    * ツールの紹介と詳細について
    * プロファイリングした後によく起きる話し
* **[4. フィルレート、Canvas、および入力](https://unity3d.com/jp/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input?playlist=30089)**
    * [フィルレート](https://docs.unity3d.com/ja/2017.1/Manual/MobileOptimisation.html)の過剰使用について
    * Canvasのリビルド・分割について
    * レイキャスト(Graphic Raycaster)について
* **[5. UI コントロールの最適化](https://unity3d.com/jp/learn/tutorials/topics/best-practices/optimizing-ui-controls?playlist=30089)**
    * テキスト・フォント・ScrollViewについて
* **[6. UI の最適化に関するその他のヒントとテクニック](https://unity3d.com/jp/learn/tutorials/topics/best-practices/other-ui-optimization-techniques-and-tips?playlist=30089)**
    * Layout・Canvas Renderer・Cameraについて

　
Unity UIのパフォーマンス調査や最適化の内容だけで、これだけの要素が出てきました。
おそらく、CPUやGPUへの処理でゲームシステム（バトルやAIなど）が半分以上必要とするゲームやそうなってしまって最適化に困難になってしまい、それでもメモリに若干空きがあったり、CPUが30FPSギリギリでなんとか動く状況があったとします。
ですが、意外とUI周りがボトルネックになってしまい、実機で動かして見ると全然遊べない...なんてことはよくあったりします。
別にゲームだけじゃなくても、VR/ARなどのコンテンツアプリでも同じ話になります（ポケモンGOのUIデザインってすごくシンプルですよね？）。


### What should I do
さて今回は、事の発端が起きる前にボトルネックになりがちな要素を避けるために「こうしたらいいよ〜」とか「あぁしたらいいよ〜」とかを話していきます。**パフォーマンス調査に関しては、また別の話なので今回は話しません。**

また、これだけ要素が多いと「どこから何を覚えてどうすれば良いのかわからない...」となってしまいがちですよね。しかも公式の翻訳が、たまに意味がわからない文章になっていたりするので、今回は、ボトルネックで一番関わっているであろう**Canvas**だけにフォーカスして話を進めていこうと思います。


### Canvas
　　　<img src="https://qiita-image-store.s3.amazonaws.com/0/82601/6964e829-bc92-91b1-8f8c-9601ddbe30b2.png" width=40%>

Canvasの設計には、「**Sub-canvas**」と「**兄弟Canvas**」の2種類になると言われています。

**兄弟Canvas**は、主にヘッダーとフッダーや左右サイドでメニューまたはステータスなどを表示させるときに分割する設計です。
**Sub-canvas**は、1つのCanvasの中に複数のサブCanvasを分割した中にScrollViewやメニューを配置していく設計です。

なぜこの分割が必要になってくるのかは、Unity UIでボトルネックを経験した人でなければなかなかわからないでしょう。公式で解説されているのは、**如何にCanvasを分割して負荷を分散させるかということ**なんですが、「**分散させる＝Canvasの分割**」は、_ボトルネックになっている負荷の原因によって、Canvasを分割しても解決できない場合_があります。

### Why?
「**分散させる＝Canvasの分割**」をただやるだけでは問題解決にはならないお話しですが、そこについてはそれこそ上記の各ベストプラクティスの内容で語られています。

例えば、このゲームの画面でどこがどうなっていたらボトルネックになり、Canvasのバッチビルド & リビルドが頻繁に起き、Canvasの分割をしなくちゃいけないのかを話してみましょう。

![049c25728eee1a776930837f5d600a70.jpg](https://qiita-image-store.s3.amazonaws.com/0/82601/4b2c4f36-ba84-f9b8-ba9a-7f2d041e8c89.jpeg)
※参照: http://smartphone-netgame.com/2016/02/10/wonder-tactics/

 
状況についてたとえば...

* 例1. 各文字(Textコンポーネント)に、**Shadow**と**Outline**のコンポーネントが付いている。
* 例2. 例1の状態で右側の時:分:秒の秒がカウントされている。
* 例3. キャラクターたちが自由に動いている。
* 例4. ニュースがある条件(Nのマークがついている)の時だけ、大きくなったり小さくなったりするアニメーションがあり、Tween制御が付いている。


### What will happen?
これらの例がこの画面で処理されていたとき、Canvasのバッチビルドが頻繁に行われ、リビルドが起きてパフォーマンスに問題が起きていると思います。この画面上のCanvasの作りが以下のような作りだったとします。

![1-2-1.png](https://qiita-image-store.s3.amazonaws.com/0/82601/9d958812-71c0-0dba-94cb-e2d5f0825820.png)

* 1. 背景: 赤枠
* 2. キャラクター: 青枠
* 3. ステータスやメニューなど: 緑枠

この順番でCanvasが作られ、各UI要素が作られているとします。
このとき、パフォーマンスを悪化させているCanvasが2と3になります。



### Looking for the culprit.
まず、Canvasのリビルドについてですが、これはどんな作りでも定期的にリビルドされるようになっています。というのも、Canvas上で何か要素が動いたり変わったりすれば、描画するためにリビルドは走るようになっています。

普段、このリビルドはそれほどまでにボトルネックにならないのですが、[Canvasがダーティでバッチビルドも同じく処理され、その次にリビルドされるような状況](https://unity3d.com/jp/learn/tutorials/topics/best-practices/fundamentals-unity-ui?playlist=30089)であれば、そこに処理待ち状態が起きたり、CPU・GPUへ大量に処理を流している状態であれば、余裕でボトルネックになります。

そして気になる犯人ですが、2と3のCanvasがボトルネックになっており、**キャラのアニメーション**と**各ステータス(キャラ以外)の更新**がCanvasのバッチビルドに処理をリクエストしていたことが今回の元凶となります。
バッチビルドが頻繁に処理される場合、そのCanvasはダーティであることになりますが、2と3のCanvasがそんな状況になっていたのは、例1~4までの内容があるからです。特に1がボスだったりします笑

### It Optimizing!!
では、Unity UIでこれらの問題があるときに必要な最適化について解説して終わりにしたいと思います。

![2-1-1.png](https://qiita-image-store.s3.amazonaws.com/0/82601/434f443f-f507-3df5-7578-db1d17006f93.png)

まずCanvasの分割についてですが、今後の事を考え、2と3が両方とも分割されています。3は、兄弟Canvasパターンを使い、2は、Sub-canvasパターンにしてあります。

そして、もしこのときに大きな変更ができない状況であれば、アニメーションを改修したり仕様を変更するよりも、**ShadowとOutlineのコンポーネントを外し**、フォントや画像で対応する対応が必要です。これで、各要素のアニメーションはCanvasの分割とともに負荷が分散され、バッチビルドはそれほど処理されない状況になると考えられます。


#### BatchBuild & Rebuild
[Unity UI の基礎](https://unity3d.com/jp/learn/tutorials/topics/best-practices/fundamentals-unity-ui?playlist=30089)で話されていますが、Canvasがダーティでるとバッチビルドが処理され、リビルドがUnity側で勝手に処理を始めたりします。これはCanvasがダーティなら、もうどうしようもありません。

そのため、如何にしてバッチビルドを頻繁に処理させず、最初の描画で処理した時のバッチビルドのキャッシュを使い回すような状態を作るか。または、処理されるのはしょうがないから、Canvas内の要素を減らして如何にバッチビルドの処理を減らすか、などのそのときの状況にあった工夫が必要になります。

　
## まとめ
* Canvas上の要素を減らす = Canvasを分割
* Canvasを分割するときは、どこがボトルネックになっているのかハッキリと把握した上で作業すること。
* テキスト関連もそうだけど、画像のRaycastTargetをオフにし、無駄な処理を減らす。