Unityでつまづいたこととその対処

# Unity5.6 -> Unity2018移行メモ

* テクスチャフォーマットの追加
    * 特にiOSでETCが使えるようになった点が大きい。<br>Android/iOSで統一設定かつiOSで長方形テクスチャが使える。
        * 不透明画像：ETC1 RGB Crunched
        * 不透明画像解像度優先：ETC2 RGB
        * 半透明画像：ETC2 RGBA Crunched
* AssemblyDefinitionによるdll分割
    * リコンパイル時間の短縮
        * Roslyn導入によってdll分割しなくてもよいかもしれない
    * セキュリティツールによってはdll分割すると暗号化ありなし設定が楽になる
    * フォルダ単位でdll分割となるので、フォルダ構成は浅く広くがよさそう。
    * MacのIL2CPPではDLL名がC++ファイル名になって特定しやすくなる
    * asmdefをいじると頻繁にプロジェクト設定が変わるのかVisual Studio/Riderが重くなる
        * VS Codeが一番軽快に動いた
* .NET4.5使用可
    * 古いUniRxとは競合が多いのでソースコード修正が大変かも
    * 関数呼び出し元情報埋め込みが熱い
    * 文字列変数埋め込みが楽でミスしにくい
    * async/awaitでEditor側の並列処理記述が超楽
        * ROM側処理でasync/await使うかはUniRX作者のブログ見てから考えたほうがよさげ
* システムdllのメモリ消費量が増えたようだ
* ROMビルド速度向上
    * これは本当助かる
* PlayerLoopで性能測定がしやすくなった
    * https://github.com/wotakuro/MainLoopProfilingSample
* バージョン次第でUnity@macのPlayデバッグがまともに動かない
    * Metal Validationのチェックを外すと回避できる
* リリースノートが無視できないので、ちゃんと見ないといけない。
    * iOSでETC使えるよとかさらっと書いてある

# 継続的なアップデートで問題となった点

* ソースコード量増加によるdllサイズ増
    * セキュリティツールでdllデカすぎエラーが出た
    * IL2CPPレイヤの関数初期化でメモリを大量に消費していた
        * Genericクラス内Genericクラス、Genericメソッド、ラムダ式などで型大量生成が原因くさい
        * 数十MB単位でメモリを食うので、Dictionary\<string,object\>などで型情報消去して避ける
            * LinqやUniRxも要注意。ラムダ式の塊
            * 型情報を整理したら数十MBメモリが空いた
        * 仕様追加でC#が増えていくと積む。コアロジック以外はLuaで実装するほうがよさげ
            * あとでLua化はほぼ無理なので最初からLua前提
            * https://github.com/Tencent/xLua
    * FlatBuffersはクラス増加が痛
        * 仕様上避けられない。
        * どのあたりでdllサイズを食っているかはリフレクションのGetILAsByteArrayであたりをつけた
* アセットサイズ増によるリソース圧迫
    * アイコンが追加され続け表示枚数が膨れた
        * 大量アイコン表示UIはリソース解放タイミングを最初から考えておく
        * Spriteを解放したらSprite.txtureも解放する
            * そもそもTexture差し替えだけであればSpriteでロードする必要がなかったり
    * 本当にSprite Atlasが必要？
        * なんでもAtlas化していたが、AnimeでないSpriteは同時/連続表示されず無駄読みになっていた
        * Atlas解除してMB単位でメモリが空いた
* シェーダーの修正は多くのアセットバンドルに影響がでる
    * １シェーダーの修正でアセットバンドル再DL量が増える
    * シェーダーの作りはSEGAのブログを参考に。
        * http://techblog.sega.jp/entry/2017/10/25/100000
* DB拡張でDB要素間矛盾バグ多発
    * Excel同時編集とブランチ間マージで大問題
    * DB要素のチェック機能は大事。
    * MongoDB/CompassでDB管理すると幸せな気がするが使ってはいない
    * これだという資料がないので誰か安定した実例を出してほしい
* 新端末/OSの挙動確認が後手後手
    * x86はリリース対象外で準備していなかったらエミュレータが使えない羽目に
    * エミュレータはx86前提なのでarmのみとしないが吉
    * Android API LvやiOS OS Verでメモリ使用量が大きく変わることがある
        * 殆どの場合メモリに余裕がなくなる
* Android API応答値がOSverで違った
    * Android.mkとか使ったネイティブプラグインで書いていたがデバッグ面倒
    * 非Unity機能なプラグインならAndroid Studioでプラグイン書くとよい
        * プラグイン単体でのデバッグが楽
* ビルド環境の整備
    * 最初はROM/アセット/アップロードひとまとめスクリプトだったがROMだけ焼きたいとか欲が
    * Ruby RakeでROMタスクアセットタスクと分割
        * 全ビルドは複数タスクの結合とした
        * ビルドパラメータはRakeデフォだと辛いので以下を参考にoptparse経由
            * https://qiita.com/mechamogera/items/ab1d212ac726f620fd8f
* Android＞GooglePlayプラグインなど更新が面倒くさい
    * PlayServicesResolverよりGradleビルドにして、implementationで解決すればいい気がする。
    * -unity.srcaarはGradleが解決できないので、.srcaarだけPlayServicesResolverに任せる。
    * 64K参照問題もGradleビルドに任せると最適化が入り起きにくいようだ

# 参考ソースコード

|概要              |                   |                   |
|:-----------------|:------------------|:------------------|
|Unityソースコード  |https://github.com/github-for-unity/unity | |
|拡張の実装例       |http://gram.gs/gramlog/creating-clone-unitys-console-window/  |各種GUIの実装例があって参考になる。|
|.csを行指定で開く  | AssetDatabase.OpenAsset(ファイルパス,行数) |       |
|UnityとGithub連携|https://github.com/github-beta/unity-preview||
|見やすいメモリプロファイラ|https://github.com/PerfAssist/PA_ResourceTracker|みやすい最高|
|Tree表示|https://docs.unity3d.com/Manual/TreeViewAPI.html|Unityが公開している|
|GUIDからアセットを開けると捗る|AssetDatabase.GUIDToAssetPath|.metaにはGUIDが書かれているので|
|C#ソースコード|https://referencesource.microsoft.com||
|C#->IL|https://sharplab.io||



# 設定ファイル

|概要              |                   |                   |
|:-----------------|:------------------|:------------------|
|設定はScriptableObjectがオススメ|Inspectorでいじれたり何かと便利|| 
|JSON,CSVは性能を気にする箇所では使わない|Flatbuffersとか||
　
# ファイル圧縮

- zipは標準では入っていない
    - アセットバンドルのlz4オプションで十分かもしれない
    - どうしても使いたい場合はdotnetzip
    - zlibで複数ファイルまとめる適当なの作ってごまかし。https://github.com/Oyayubi11/SimpleZipForUnity

# Network

- System.Net.WebClientを使うとTLSv1になる
    - .NET4で解決した。
    - 5.4系までは.Netのバージョンが古く避けられないかも
    - C#で完結させるなら素直にUnityWebRequest使う方がよさげ
        - TLSv1.2になるし認証もいい感じにやってくれる

# デバッグ

- スマホ環境だけでなく、開発マシンでも実行できるとよい
    - Windows向けビルドができるとメモリ解析がしやすい。スマホのManaged領域を解析するのは楽ではない。
- OnGUIは結構使いにくい
    - 解像度あわせとか面倒くさい。実機をタッチで操作するのは結構面倒
    - リモートデバッグでコンソールから操作できると楽。特にパラメータがあるとき。
        - これとか便利。ただ負荷が高い
        - https://www.assetstore.unity3d.com/jp/#!/content/12294
    - リモートから操作できるようになると自動化が捗る
    - iOSはXCodeないとログ抜きにくいのでリモートから色


# 基本ワークフロー

- 最低スペック端末を普段から使うこと
    - 性能のいい端末だけでテストしていると性能劣化を見逃しがち
- 定期的にプロファイラログを残しておく
    - どの時点から性能劣化したか比較して探しやすい。
- GameObject数などの目安を共有して超えないようにしてもらう
    - 演出追加などで簡単に制限を超えてくる
    - たぶん2017年ミドルスペックスマホだと以下が限界値
        - UnityEngine.Object数万個,
        - UnityEngine.GameObject数千個
- アセットImport時、自動でデータをチェックする機能を早めに作っておく
    - テクスチャ設定ミスなど後で洗うのは面倒くさい

# 性能で気をつけたいこと

- メモリ使用量は超大事
    - 使いすぎるとOSからアプリが殺される。
        - iOS 1GB端末は500MBあたりから不安定。
    - リソースはテクスチャ、アニメーションデータが大部分を占める
        - iOS＞PVRTCはテクスチャが正方形化されるのでなるべく長方形テクスチャは避ける
        - Unity2018以降ならiOSでもETCが使える
        - SpriteAtlasサイズ2048はiOSで無駄かも。DrawCall数との天秤で1024でもいい。
            - サイズ変更はここ＞https://docs.unity3d.com/jp/540/Manual/SpritePacker.html
    - Unityメモリプロファイラを見てもIL2CPP的な無駄メモリ食いなどは見えない
        - Unityプロファイラ不安定な気がするいつも止まる。
        - テクスチャやC#のメモリは見えるが、IL2CPPレイヤは見えない。
            - IL2CPPの関数情報ロード周りで大量にメモリを食う問題はXCodeで追わないとわからなかった。
        - 最初はXCodeでざっくり増え方を見て、不自然に増えているところはUnityプロファイラで詰める
- ピクセルライトは重い。
    - Light設定でNotImportant/Autoとか動的に変えられるので試す
- 解像度を落とせるなら落とす。
    - AndroidはSoCの性能の割に無駄に解像度が高いのでは？330dpiで十分なのでは？
- アンチエイリアスはなし？4x4?8x8?
- GameObject.nameが地味にメモリを食う。名前管理は避ける
- 動的ロードしたUnityEngine.Object系は使い終わったらnull代入すること
    - リークの原因
- Unity標準APIはメモリを食うことが多い
    - RuntimeAnimationClip参照でひどい目にあった気が
    - 頻繁に触るUnityの情報はキャッシュできるならしておいた方がいい
    - RaycastAllNonAllocなどヒープ節約版を使う
- できるだけstruct化
    - 小さいデータはスタックに置くことを心がける。ヒープに一時データを多数置くとGCでやらかす
- GCは気づかれないタイミングなら自前で叩くのもあり
    - ロード画面出してて多少止まっても怒られないなら叩いてメモリ整理もよい
- 文字列操作がとにかくメモリ負荷を上げる
    - なるべくハッシュ化するとかStringBuilder使うとか
    - BeginWithとかも何気に重い。ローカライズ考慮しないオプションを第二引数に指定すると避けられる？
    - ログ出力は#ifかConditional属性で引数評価から削れるようにしておく
        - 引数評価の文字列生成でプロファイラのヒープ消費量がみにくいことがある
- enumのboxingが地味に痛い。C++の感じで使うと痛い目を見る
    - (int)キャストが入るなら使わない方法を考える。
        - Dictionaryのキーにenumを使うのもまずい
        - IEqualityComparatorで回避可だったような
    - public static readonly int HOGE_STATE_A = 0;みたいな定数でいいかも。
- 本当にList?配列のほうが負荷低い。
    - Listのforeachの無駄メモリは5.6系から直った
    - ListのforeachはIL/IL2CPPでtrycatchが入っている
    - 配列のforachとforは同じになる。
- List.ForEachはいいことがない。foreachのみ
    - ラムダ式が渡されるので無駄にActionが生成される。削れるはず。
- sealed classは積極的に使う
    - IL2CPPで最適化が入る
- ()キャストよりasキャストのほうが負荷低い
- CSVを使おうとは思ってはいけない。テキストパースは重い
    - JSONも改善されたとはいえ重い
    - ZeroFormatterとかFlatBufferとかバイナリベースがよい
- 5.4系からInstantiateに親GameObject,座標が渡せる。
    - パフォーマンスもあがる
- LINQはソースコード可読性向上に役立つが内部でいろいろやってるので注意
    - ちょっとしたSelectやWhereならforeachで十分なはず。
    - 毎フレームなんども叩かれる処理で使うのは性能的によくないかも
    - Genericで大量にラムダ式型が変わる場合は要注意。IL2CPP関数情報でメモリを食う。
- iOSが苦手なシェーダーの書き方をしない
    - clipを使ったりdiscardシェーダーは重い。alphablendのほうがマシ
- XCode超優秀
    - 特にGPUプロファイルで効く。一次切り分けはXCodeでいいかも
    - iPhone5S以降だと負荷分析が賢い。ダメなシェーダをわかりやすく教えてくれる
- Tegra GPU Debugger超優秀
    - Adreno系SoCが多いがプロファイラが結構不安定
    - XCode並みに解析してくれるのでAndroid開発のときはTegraK1なタブがあると幸せになれそう
    - Tegraなタブレットが少ない・・・
- オーバードローを避ける
    - 一度フラグメントシェーダーで書いた箇所に上書きで書かれると塗りつぶされたところは無駄
    - 描画順に気をつける
    - UnityEditorでオーバードローヒートマップが出せるので確認しておく
- renderer.materialはマテリアルのコピーを作る。SetPassが増える。
    - パラメータが見たいからといって叩くと危険
    - マテリアルのパラメータ変えはMaterialPropertyBlockを使うとマテリアルインスタンスを増やさなくて済むがSetPassは節約できない。
- 頂点情報に不要なUV値や法線情報が残っていないか？SetPass節約が効きにくくなる
- モデルのマテリアルは共通化できないか？別テクスチャ参照別マテリアルより１まとめなテクスチャ１マテリアルをUVずらしで使うのがよい
- 頂点数が多いとSetPass節約が効かなくなる。
- GPUスキニングチェックが入ってると想定外のGPU負荷が出たりする

# デバッグログを消す

```
#if !DEVELOPMENT_BUILD
public static Debug
{
  [Conditional("DEVELOPMENT_BUILD")]
  public static void Log( string s, object o = null )
  {
  }
}
#endif
```
とグローバルネームスペースに空のDebug.Logを置いて
```
using UnityEngine;
...
  void hoge(){
    Debug.Log("hoge"); //UnityEngine.はつけない
  }
```
とするとリリースビルドのときはグローバルネームスペースのDebug.Logが優先されてLog呼び出しが消えるはず。引数評価もされなくなるはず。

UnityEngine.Debug.logger.logEnabledでも出力は止められるが
関数評価がコンパイル時に削られるわけではないので引数の処理が負荷として残り続けるはず

- logEnabled = false
- 上記方法

でログを消せるはず。それでも怖いなら#ifでログを囲うこと。

```
  public static class LogWrapper
  {
    [Conditional("DEVELOPMENT_BUILD")]
    public static void Log(string s)
    {
      UnityEngine.Debug.Log(s);
    }
  }
```
とするとEditorデバッグ時コンソールからログ出力箇所へ飛べなくなるのでデバッグ効率が下がる。おすすめしない。

ちなみにUnityEditorの設定のLoggingで消せるかと思ったらあれはStacktraceの出力設定だった。

# アセットバンドル
- AssetBundle.LoadFromMemoryは独自暗号をかける際には必須
    - 元データからUnity側へメモリコピーが走るのでメモリ負荷注意
        - LoadFromMemory後は元データは破棄して問題ない
    - ChunkオプションつけてLZ4化必須と思われる
    - メモリコピーが走ることを考えるとアセットバンドルのサイズは数MBが上限と思われる。
- 共通データは共通アセットバンドルとしてまとめる。でないといろんなアセットバンドルにコピーされる。
- アセットバンドルのくくりは.metaに名前をつけるよりもスクリプトでアセットパス一覧を渡す方がよい。.meta管理は大変。
- AssetBundleがResources配下のシェーダ等リソースを参照していると、ビルド時Resources配下からAssetBundle内にコピーされる
    - フォントならフォントが各アセットバンドルにコピーされたり
    - 共通AssetBundleにしておくと各アセットバンドルへのコピーを回避できる
    - 類似としてテクスチャのアトラス単位も注意。アトラス単位で共通テクスチャとして共有アセットバンドル化するとよい
- アセットバンドルフォルダ構成は浅く広くがよさげ
    - アセットバンドル間のデータ依存が追いにくい
    - 追加データを別アセットバンドルにしにくかったりする
- フォント、シェーダーなどは共通アセットバンドルとして独立させる
    - 独立させないと各アセットバンドルにコピーされる。
- AddressableAssetBundleは必須ではない
    - アセットバンドルフォルダ構成など独自ルールにする場合は自作でも全然OK
    - BuildPipelineなどは活用してよさげ

# フレームワーク
- UniRxが素敵なので使う方がいい
    - 作者のブログも素敵なので読んで参考にすると良さげ
    - UnityEventをSubscribeしたらDisposeしないとメモリリークするので注意
- 共通的なステートマシーンクラスをみんな使う
    - 各自Update関数の中でswitch-caseするのはよくない

# 環境設定
- Unity5.6からROMデータ圧縮オプションが増えたようだがそこまで効果はない
- アセットインポート(特にテクスチャ)が遅い
    - Preferences > General > Compress Asset...のチェックを外すと改善されることがある。
    - テクスチャ圧縮品質はNormalでだいたい足りる。Bestが多いと時間がかかる。
- Application.persistentDataPathはiOSではiCloudのバックアップ対象になることがあるらしい
    - iOSだけバックアップ対象外オプションをつけるとよいらしいが試してない
- Resourcesに置くデータは必要最小限に
    - 問答無用でROMに含められる
    - とにかくROMビルド時間が伸びる
    - UnityEditorデバッグ時、アセットバンドル内のデータをResouces.Load(Resourcesフォルダを切る)してはいけない
        - 公式DocのとおりAssetDataBase.LoadAssetAtPathを使う
        - EditorデバッグでロードしたいからとアセットバンドルにResourcesを切るとROMに含まれる(当然
- さっくりROM確認したいときはAndroidではMonoビルドするとすぐ終わる
    - IL2CPP用処理が結構時間食う
    - Editor拡張でビルド設定を選べるようにすると楽
    - PlayerSettings.SetPropertyInt ("ScriptingBackend", (int)ScriptingImplementation.IL2CPP, BuildTargetGroup.iOS); 
- UIが重い
    - どうしようもないところがある
    - Xamarin + CocosCShapのほうが幸せになれるのでは
- Androidの場合ATOMスマホは対象？ARMだけ？
    - ネイティブプラグインコンパイル条件が変わりのプラグインサイズが変わる＝ROMサイズが変わる。
- JenkinsからIL2CPPビルドしようとするとUnable Detected NDKとか言われる
    - UnityEditorから実行すると成功するしコマンドプロンプトで叩いても成功する
    - どうもJenkinsからビルドするときだけNDKわからんと言われる
    - EditorPrefs.GetString("AndroidNdkRoot")を見ると空
    - EditorPrefs.SetString("AndroidNdkRoot", path)で設定するとビルドできた