C#
Unity

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

Editor拡張関連

概要
拡張の実装例 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が書かれているので

設定ファイル

概要
設定はScriptableObjectがオススメ Inspectorでいじれたり何かと便利
JSON,CSVは性能を気にする箇所では使わない Flatbuffersとか

 

ファイル圧縮

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

Network

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

デバッグ

  • スマホ環境だけでなく、開発マシンでも実行できるとよい
    • Windows向けビルドができるとメモリ解析がしやすい。スマホのManaged領域を解析するのは楽ではない。
  • OnGUIは結構使いにくい
    • 解像度あわせとか面倒くさい。実機をタッチで操作するのは結構面倒
    • リモートデバッグでコンソールから操作できると楽。特にパラメータがあるとき。
    • リモートから操作できるようになると自動化が捗る
    • iOSはXCodeないとログ抜きにくいのでリモートから色

基本ワークフロー

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

性能で気をつけたいこと

  • メモリ使用量は超大事
    • 使いすぎるとOSからアプリが殺される。
    • ほとんどの場合テクスチャがメモリを食う。無駄にロードしてないかなどプロファイラから探す
    • アセットは使い終わったらUnloadする方針がよい。GC任せだとリークしたとき追えない。
  • ピクセルライトは重い。
    • Light設定でNotImportant/Autoとか動的に変えられるので試す
  • 解像度を落とせるなら落とす。
    • 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を使うのもまずい
    • public static readonly int HOGE_STATE_A = 0;みたいな定数でいいかも。
  • Listのforeachの無駄メモリは5.6系から直った
  • CSVを使おうとは思ってはいけない。テキストパースは重い
    • JSONも改善されたとはいえ重い
    • ZeroFormatterとかFlatBufferとかバイナリベースがよい
  • 5.4系からInstantiateに親GameObject,座標が渡せる。
    • パフォーマンスもあがる
  • LINQは大事。なるべく使う。
    • 毎フレームなんども叩かれる処理で使うのは性能的によくない
  • iOSが苦手なシェーダーの書き方をしない
    • clipを使ったりdiscardシェーダーは重い。alphablendのほうがマシ
  • XCode超優秀
    • 特にGPUプロファイルで効く。一次切り分けはXCodeでいいかも
    • iPhone5S以降だと負荷分析が賢い。ダメなシェーダをわかりやすく教えてくれる
  • Tegra GPU Debugger超優秀
    • Adreno系SoCが多いがプロファイラが結構不安定
    • XCode並みに解析してくれるのでAndroid開発のときはTegraK1なタブがあると幸せになれそう
  • オーバードローを避ける
    • 一度フラグメントシェーダーで書いた箇所に上書きで書かれると塗りつぶされたところは無駄
    • 描画順に気をつける
    • UnityEditorでオーバードローヒートマップが出せるので確認しておく
  • renderer.materialはマテリアルのコピーを作る。SetPassが増える。
    • パラメータが見たいからといって叩くと危険
    • マテリアルのパラメータ変えはMaterialPropertyBlockを使うとマテリアルインスタンスを増やさなくて済むがSetPassは節約できない。
  • 頂点情報に不要なUV値や法線情報が残っていないか?SetPass節約が効きにくくなる
  • モデルのマテリアルは共通化できないか?別テクスチャ参照別マテリアルより1まとめなテクスチャ1マテリアルを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にしておくと各アセットバンドルへのコピーを回避できる
    • 類似としてテクスチャのアトラス単位も注意。アトラス単位で共通テクスチャとして共有アセットバンドル化するとよい
  • アセットバンドルフォルダ構成は浅く広くがよさげ
    • アセットバンドル間のデータ依存が追いにくい
    • 追加データを別アセットバンドルにしにくかったりする

フレームワーク

  • 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)で設定するとビルドできた