Unityモバイルゲームの最適化についてのメモ

# 初めに

Unityから、モバイルゲーム向けの最適化に関する電子書籍が出たので、
翻訳しつつ、自分が重要だと感じたところをメモ。

https://discussions.unity.com/t/two-new-optimization-guides-for-mobile-console-and-pc-2022-lts-edition/318797
## プロファイリング
Unityの代表的なプロファイリングツールには下記がある。

- Unityにもともと組み込まれているProfiler
- [Memory Profiler](https://docs.unity3d.com/Packages/com.unity.memoryprofiler@1.1/manual/index.html)
    - メモリの分析に特化したツール
- [Profile Analyzer](https://docs.unity3d.com/Packages/com.unity.performance.profile-analyzer@1.2/manual/index.html)
    - Unityにもともと組み込まれているProfilerで分析した結果をさらに詳細分析できるような、補助ツール
    - これで、「最適化前」と「最適化後」の分析結果を比較できるらしい(良さそう)

Memory ProfilerとProfile Analyzerについては、別途packageマネージャーからインストールしないと使えないので注意。

IOSの場合、Xcodeの[Instruments](https://help.apple.com/instruments/mac/current/#/dev7b09c84f5)が使える。

 Android / Armの場合、Android StudioのAndroid MonitorやAndroid Profiler。
 Arm Mobile Studio、Snapdragon Profileなどがある。


下記はボトルネットを見つけるフロー。
 
![Unity資料.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208277/0167cd79-0c4a-804e-2820-798623603b93.png)


### Unity Profilerの注意点

ディーププロファイルモードを使うと、関数の呼び出しと終了を正確に分析できるメリットがあるが、
各関数にオーバーヘッドが上乗せされるため、プロファイル結果が歪む可能性がある点に注意。

ある程度原因が絞り込めたら、ディーププロファイルモードを解除して計測するのがよさそう。

### プロファイル全般の注意点

端末で長時間プロファイルするのは避けよう。
プロファイルは不可の高い処理なので、端末の温度が上がりやすい。
端末の温度が上がった結果、性能が低下しプロファイル結果が歪んでしまう可能性がある。

### CPUボトルネックか、GPUボトルネックなのかの判別

Gfx.WaitForCommandsが頻繁に実行される場合、CPUがボトルネックとなっている可能性がある。

Gfx.WaitForPresentが頻繁に実行される場合、GPUがボトルネックとなっている可能性がある。

## メモリ

### GCを避ける

- 文字列：
    - 不要な文字列の作成や操作を減らす。JSONやXMLのような文字列ベースのファイルのパースを避ける。
    - 代わりにScriptableObjects化したり、MessagePackやProtocolBuffers を使おう
    - 実行時に文字列を作成する場合は、StringBuilderを使おう
- コルーチン：
    - WaitForSeconds はキャッシュして再利用する
- LINQと正規表現：
    - パフォーマンスに問題が出るなら、これらの利用を避けること

GCについての更なるベストプラクティスについては、下記を参照とのこと。
https://docs.unity3d.com/Manual/performance-garbage-collection-best-practices.html

### インクリメンタルなGCを有効化する

`Unity-> Project Settings -> Player`から`Use incremental GC`を有効にすることで、GCのスパイクを分散させ、急激にGGの負荷がかかるのを軽減できます。

## プロジェクトの設定
### 加速センサーを無効化、または削減する
`Unity-> Project Settings -> Player`から`Accelerometer Frequency`を無効にするか、頻度を下げる。

### 不要な物理演算機能を無効にする
Auto Simulation とAuto Sync Transformsの設定を無効化する

### 大きな階層を避ける
Gameobjectの不必要なネストは避ける。
ネストが深いと、不要なTransformの計算が発生し、ガベージコレクションのコストが増えます。

### Transform の頻繁な更新を下げる
Transformの位置と回転を同時に設定する場合は、`Transform.SetPositionAndRotation`関数を使う。

GameObjectを生成する際に、同時にParentと位置、回転の指定をする。

```csharp
GameObject.Instantiate(prefab, parent, position, rotation);
```

## アセット

### テクスチャを正しくインポートする

- Max Sizeを視覚的に許容できる最小のサイズに設定する。
- テクスチャのサイズは2のべき乗にする。
    - テクスチャ圧縮フォーマット（PVRCTやETC）のベストプラクティス。
- テクスチャをアトラス化する
    - ドローコールを減らし、レンダリングを高速化できます。
- Read/Write Enabled オプションをオフ
-  不要な Mip Maps 設定をオフ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208277/a20e155f-f36e-bdfa-5b77-3fcf70a656a2.png)



### テクスチャを圧縮する

可能なら、iOSとAndroidの両方でASTC圧縮を選択する。

ターゲットとなるIOSデバイスのCPUがA7以下なら、PVRTCを使用。
ターゲットとなるAndroidが2016年以前のデバイスなら、ETC2を使用。

プラットフォームごとの推奨フォーマットの詳細は下記を参照とのこと。
https://docs.unity3d.com/Manual/class-TextureImporterOverride.html

### メッシュのインポート設定

- メッシュを圧縮する
    -  積極的に圧縮することで、ディスク容量を減らすことができる
(ただし、実行時のメモリは影響を受けません）。
    - 圧縮レベルによってはメッシュが不正確になる可能性がある。
- Read/Write: Enabledを無効化する
- 不要ならRigの設定を無効にする
- 不要ならBlendShapesの設定を無効にする
- 不要ならnormals と tangentsの設定を無効にする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208277/0a40b461-9e9e-78ef-813c-7f9cab8374df.png)

## グラフィックとGPU
### 描画負荷の削減について
- SRP Batching
  -  HDRPまたはURPを使用している場合、SRP Batchingを行うことで、ドローコール間のGPUセットアップを削減し、マテリアルのデータをGPUメモリに保持して使いまわすことで、CPUレンダリング時間を短縮できる。
- GPU instancing
  - 同じオブジェクトが多数ある場合（同じメッシュとマテリアルを持つ建物、木、草など）、
GPUインスタンス化を使うことで。グラフィックハードウェアを使用してオブジェクトを一回のドローコールでバッチ処理できます。
- Static Batching
  - 移動しないオブジェクトは、インスペクタの設定から、「Batching Static」を有効にすることで、
ゲーム実行時に「Batching Static」を有効にしたメッシュを結合して、１つのメッシュにできる。
- Dynamic Batching
  - 同じようなマテリアルを適用したメッシュをリアルタイムに結合して、１つのメッシュにできる。

## UI
### Canvasを分割する。
Canvasで描画している要素１つでも更新があると、
Canvas全体の座標が再更新されてしまうので、
Canvasは要素の更新頻度別に分割する。

### 見えていない要素は隠す
不可視のUI要素は、アクティブにしているとドローコールを使用する可能性がある。
なので、不要な時はUIコンポーネントをdisableにしておこう。

### GraphicRaycasterの階層に気を付ける
GraphicRaycasterは取り付けられたオブジェクトから、
再帰的に下を辿っていき、RaycastTargetが有効なオブジェクトを検索するので、
Canvasのトップレベルとかに付けていると無駄に検索コストが増える。
そのため、GraphicRaycasterは必要最低限の階層につける。

### RaycastTargetは必要最低限に
RaycastTargetが不要なUIはRaycastTargetを無効にする。

### LayoutGroupはなるべく避ける。
レイアウト・グループは更新効率が悪いので、使用は控えめに。

動的な要素にレイアウト・グループ（水平、垂直、グリッド）を使用する必要がある場合は、
入れ子を避けてください。
（それが出来たら苦労しない。。。）

### 大きなリスト・ビューとグリッド・ビューは避ける
大きなリスト・ビューやグリッド・ビューは高コスト。
数百のアイテムがあるようなリストビューを作成する必要がある場合、
UI 要素を再利用することを検討しましょう。

### 多数の要素を重ねない
多くのUI要素（例えば、カードバトルゲームで積み重ねられたカード）を重ねると、過描画になります。
レイヤー化された要素を実行時にマージするようにコードをカスタマイズしましょう。
より少ない要素とバッチになるようにコードをカスタマイズします。
（それが出来たら苦労しない。。。）