Help us understand the problem. What is going on with this article?

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

Unity5.6 -> Unity2018移行メモ

  • テクスチャフォーマットの追加
    • 特にiOSでETCが使えるようになった点が大きい。
      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で性能測定がしやすくなった
  • バージョン次第でUnity@macのPlayデバッグがまともに動かない
    • Metal Validationのチェックを外すと回避できる
  • リリースノートが無視できないので、ちゃんと見ないといけない。
    • iOSでETC使えるよとかさらっと書いてある

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

  • ソースコード量増加によるdllサイズ増
    • セキュリティツールでdllデカすぎエラーが出た
    • IL2CPPレイヤの関数初期化でメモリを大量に消費していた
      • Genericクラス内Genericクラス、Genericメソッド、ラムダ式などで型大量生成が原因くさい
      • 数十MB単位でメモリを食うので、Dictionary<string,object>などで型情報消去して避ける
        • LinqやUniRxも要注意。ラムダ式の塊
        • 型情報を整理したら数十MBメモリが空いた
      • 仕様追加でC#が増えていくと積む。コアロジック以外はLuaで実装するほうがよさげ
    • FlatBuffersはクラス増加が痛
      • 仕様上避けられない。
      • どのあたりでdllサイズを食っているかはリフレクションのGetILAsByteArrayであたりをつけた
  • アセットサイズ増によるリソース圧迫
    • アイコンが追加され続け表示枚数が膨れた
      • 大量アイコン表示UIはリソース解放タイミングを最初から考えておく
      • Spriteを解放したらSprite.txtureも解放する
        • そもそもTexture差し替えだけであればSpriteでロードする必要がなかったり
    • 本当にSprite Atlasが必要?
      • なんでもAtlas化していたが、AnimeでないSpriteは同時/連続表示されず無駄読みになっていた
      • Atlas解除してMB単位でメモリが空いた
  • シェーダーの修正は多くのアセットバンドルに影響がでる
  • 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タスクアセットタスクと分割
  • 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は結構使いにくい
    • 解像度あわせとか面倒くさい。実機をタッチで操作するのは結構面倒
    • リモートデバッグでコンソールから操作できると楽。特にパラメータがあるとき。
    • リモートから操作できるようになると自動化が捗る
    • iOSはXCodeないとログ抜きにくいのでリモートから色

基本ワークフロー

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

性能で気をつけたいこと

  • メモリ使用量は超大事
    • 使いすぎるとOSからアプリが殺される。
      • iOS 1GB端末は500MBあたりから不安定。
    • リソースはテクスチャ、アニメーションデータが大部分を占める
      • iOS>PVRTCはテクスチャが正方形化されるのでなるべく長方形テクスチャは避ける
      • Unity2018以降ならiOSでもETCが使える
      • SpriteAtlasサイズ2048はiOSで無駄かも。DrawCall数との天秤で1024でもいい。
    • 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節約が効きにくくなる
  • モデルのマテリアルは共通化できないか?別テクスチャ参照別マテリアルより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にしておくと各アセットバンドルへのコピーを回避できる
    • 類似としてテクスチャのアトラス単位も注意。アトラス単位で共通テクスチャとして共有アセットバンドル化するとよい
  • アセットバンドルフォルダ構成は浅く広くがよさげ
    • アセットバンドル間のデータ依存が追いにくい
    • 追加データを別アセットバンドルにしにくかったりする
  • フォント、シェーダーなどは共通アセットバンドルとして独立させる
    • 独立させないと各アセットバンドルにコピーされる。
  • 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)で設定するとビルドできた
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした