はじめに
はじめまして。
株式会社アカツキの新卒Unityクライアントエンジニア、@sevenstartearsです。
アドベントカレンダーのネタを選ぶのは本当に難しいですね、僕にとって、コードを書くよりも難しいかもしれません。
エディターの話をしようと思っていたが、泥沼に足を突っ込むことになりかねないのでやめました。
Unityでパフォーマンスの話をできる範囲内で少ししてみようと思います。
Unityでパフォーマンスと来たら、メモリの話がまずありますが、今回はあえてメモリにフォーカスを絞らず、uGUI回りのパフォーマンス・チューニングについて書いてみたいと思います。
初歩的なものが多いかもしれませんが、どうか暖かい目で読んで頂けると助かります。
uGUI
パフォーマンスの話をする前に、少しuGUIの使い方の話をします。
多くの場合、チューニングというレベルより以前に、使い方一つで解決できる問題もあります。
今回はアトラスの使用と使わなくなったUIリソースの処理の2点について書きたいと思います。
アトラスの使用
uGUIを使う上でアトラスを使わないということは多分ないと思いますので、「アトラスを使いましょう」などの話はいったん飛ばします。
アトラスを使う場合、使い方についてのポイントをいくつか書きたいと思います。
- 共通のUIパーツ(ダイアログの枠などをもとめて一枚から数枚のアトラス画像にすることで画面遷移するときのロード時間を減らすことができます。
- 同じ機能のUIパーツ(キャラアイコン)を一枚から数枚のアトラス画像にすることで同じく画面遷移のロード時間を減らすことができます。
- 同じUI画面のパーツのできるだけ一つのアトラスにすることでDrawCallを減らすことができます。
- 形揃ってるものと不規則なもの、透明ありのものとなしのものを別々のアトラスにすることで画像の容量と使用するメモリを抑えることができます。
- インゲームで使うパーツはアウトゲーム用のアトラスに置かないことで、インゲームではアウトゲームのUIパーツ分のメモリが解放され、インゲームでのメモリ使用に余裕をもたらすことができます。
使わなくなったUIリソース
開発していく過程で、必ず使わなくなるものが出てきます。こういった負の遺産がたまり続けるとやがて大きな負荷になってしまいます。
そういった使わなくなったリソースに対して、しっかり対策を立てていくことが重要です。
- Resourcesの下にできるだけUIリソースを置かないことで、ビルド時Resourcesの下にないかつ引用されていないUIリソースはビルドの対象から外れます。
- 使わなくなったUIやアニメーションオブジェクトをDisableなどではなく、しっかりシーンから削除するだけではなく、使わなくなったUI画像のちゃんと削除しましょう。引用を切らすことでビルドの対象から外れることになりますが、Editor上では残り続けるので、最適化判断の邪魔になりかねません。
CPU
uGUIのパフォーマンスの話をするにはCPUの話は欠かせない、今回はプロファイラーをよく見かけるCanvas.SendWillRenderCanvasesさんやCanvas.BuildBatchさんについて、少し書いてみたいと思います。
基本、UIパーツに変化が発生した場合、CanvasUpdateRegistryがCanvas.SendWillRenderCanvasesイベントが発生することをトリガーとして、LayoutとGraphicのRebuild処理を着火します。
諸悪の根源というほどではないが、このRebuild処理を如何に軽く、回数を少なく抑えるかがCPUの負担を減らす重要な課題にまちがいありません。
ヒエラルキーの階層を少なくする
実際リビルド処理に入る前に、ヒエラルキーの階層の深さ順にソートする処理が入るので、階層を少なくすることでソートの時間を減らしRebuildの時間を短縮させることができます。
静的UIパーツを切り分ける
transformやアルファーなど頻繁に変わるパーツを切り分けて特定なCanvasに突っ込むことで、Rebuildの頻度と範囲を減らすことができます。
便利だけど、使わない方が実は軽くなるかもしれないもの
- Best Fit : テキストのサイズを自動的に調整してくれる素晴らしいものですが、使われるであろうフォントサイズのグラフィックを全部入ってるatlasが生成されるので、生成の時間がかかる上に、サイズもデカくなります。
- Enable/Disable:オブジェクトのオンオフを切り替えるとき、当たり前のことだがEnable/DisableするとLayoutとGraphicのRebuild処理が入ります。何かいい方法があるのでしょうか?CanvasRendererだけをオフにできれば...
- Pixel Perfect:使うと、locationが変わる時LayouのRebuildが入ります。ScrollRectとかでこれやるとつらい思いをします。
Raycast Targetを外そう
デフォルトではすべてのUIオブジェクトに対してRaycastを取りますが、タッチ判定を必要としないもののRaycast Targetを外すことでCPUの負荷減に繋がります。
+α的な何か
ヒエラルキーの階層をあえて増やす
ヒエラルキーの階層をあえて増やすことでDrawCallを減らすことができる。画像に載せたテキストAとただのテキストBだと2つのテキストに対して、DrawCallが2回だが、Bを透明の画像(Aを載せてる画像とバッチできるやつ)の子供にすることで、テキストの階層が一致し、DrawCallを1回で済みます。ただし、前述のUIのRebuildによるCPUの負荷の関係で、使い方は状況を慎重に見極める必要があります。
空のImageを使った全画面タッチ判定を取るのをやめる
よくあるFullScrennButton的なものが空のImageのアルファーを0にして全画面サイズに設定し、ボタンを付けることで全画面のタッチ判定を取得します。
実はこのやり方は余分のOverDrawに繋がります。基本タッチ判定はGraphicであれば取れますので、画像である必要は全くありません。
Graphicを継承した簡単なクラスを作ることで、同じ処理を実現することができます。
最後に
だらだらといろいろ書きましたが
どれも「これやれば画期的にパフォーマンスが変わる!」のようなものではありません。
そもそも、そんなものがもしあれば、知りたいです。
パフォーマンスのための最適化は「塵も積もれば山となる」の言葉を信じて、コツコツやっていくしかないと思っています。
しかも、メモリ最適化しすぎると、CPUの負荷がかかり過ぎてプラマイゼロなんてことも十分ありえます。
プロファイラーと睨めっこしながら、もっとも解決すべき問題を少しつつ最適化していく苦行がやがて快適なユーザー体験につながるのであれば、それもまた楽しいことだと信じています。