Unityローディングに関して、あう可能性のある問題ーーローディング編

###キーワード
アセットのロードとアンロード
インスタンス化
アセット管理方法

---
##一、アセットローディング
**Q1: Shaderは独立してパッケージ化されていますが、ゲーム開始時に一度ロードすれば、その後シーンを切り替えるたびにロードする必要はありませんか？**
>正確に言うと、後続のShaderのローディングコストが発生しないために、次の2つの条件を満たす必要があります。
（1）Shaderの含むAssetBundleファイルがメモリに駐在します。
（2）Shaderが完全にロードされています。
これらの2つの条件が満たされている場合、後続のロードされたGameObjectは、これらのShaderに依存すれば、ロードや解析のコストはなく、直接に使用させられます。

---
**Q2: Panel AとPanel Bの2つのインターフェースがあり、どちらも共有Atlas Cに依存しているとします。AssetBundleをパッケージ化する場合、別々にパッケージ化すると、使用中にAssetBundle Cをアンインストールできませんか？Panel AとPanel Bをロードする前に、Atlas Cをメモリにロードさせましたが、AssetBundle Cをアンロードされている場合、Panel AとPanel Bをロードおよびインスタンス化するときに問題が発生しますか？**
>そうです。PanelとAtlasだけでなく、AまたはBがCに依存している限り、AやBがロードまたはインスタンス化されるときに、CのAssetBundleがメモリに存在する必要があります。Cに対するAssetBundleが以前にアンロードされている場合、AとBがロードまたはインスタンス化されると、エンジンはCをAとBに自動的にバインドして使用することができなくなります。

>AssetBundleの依存パッケージ化については、以前の記事[「AssetBundleを明らかにする」](https://qiita.com/UWATechnology/items/f04241f14d4add3389c1)で説明しました。

---
**Q3：このLoading.UpdatePreloadingとは何ですか、なぜ突然高くなるのですか？ 一般的な最適化方法はありますか？**
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/3c0af791-9c9d-e7ae-c1f8-69ed8ba371e6.png)
>これは、Unityエンジンの最も主なロード関数です。このアイテムは通常、シーンを切り替えたり、アセットを主動的に動的にロードしたりする時に大きくなります。一般的に、読み込みアセットの多いや複雑になるほど、反映されたLoading.UpdatePreloadingの時間コストが長くなります。
最適化する前に、この関数のCPU占有ボトルネックを特定する必要があります。次の図は、私たちのデモプロジェクトであり、UWAテストレポートでLoading.UpdatePreloading関数の総CPU割り当て状況が明確にみられます。このスタック情報を通じて、開発チームは関数時間コストの割り当てを一目で理解できし、特定に最適化をします。
![2-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/aefb0877-57ff-6aaf-59b6-c69a19d66b84.png)

---
**Q4：Loading.ReadObjectのコストが高いことに関して、推奨される方法はありますか？**
>Loading.ReadObjectとは、Unityエンジンのアセットローディング関数であります。一般的に、シーンを切り替える時やAPIコールをロードする時に見つめられます。これには、Texture、Mesh、Material、Shader、AnimationClipなどのアセットが含まれます。その値が高すぎる場合は、ロードされたアセットを詳しく最適化することをお勧めします。
各種アセットのローディングについては、UWAは記事の形式でまとめをしています。今後どんどんみんなとシェアします。

---
**Q5：Shader.WarmupAllShaders操作を使用しましたが、後にアセットをロードする時にCreateGPUProgramはまた見られます。（Shaderは一つのAssetBundleファイルにある、全てメモリに駐在し、削除しません）必ずShaderVariantCollectionを使用してShaderをロードしますか？**
>上記の問題の最も可能性の高い原因は、Shaderが異なるAssetBundleにパッケージ化されており、WarmupAllShadersが現在のメモリ内のShaderのみをwarm upできることです。後で別のShaderがロードされた場合でも、CreateGPUProgram操作はまだあります。

---
##二、アセットのアンインストール
**Q1: シーンを切り替えるとき、前のシーンをアンインストールすると時間がかかると感じますが、推奨される解決策はありますか？**
![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/62f58904-7f97-1f3a-cfd9-a705b76bea8a.jpeg)
>これは、Unityがシーンを切り替えるときにResources.UnloadUnusedAssets関数をコールことが原因です。通常、コストは比較的に大きいです。開発チームに、UWAパフォーマンステストを介して、ロードモジュールでさらに原因を特定することをお勧めします。
![4-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/5f5b2e5e-9c3b-9ed5-95a3-9ebe42f3feb5.png)

---
**Q2：Resources.UnloadUnusedAssets（）は、Resources.Loadのアセットだけでなく、AssetBundle.Loadのアセットも含まれていますか？**
>そうです、Resources.UnloadUnusedAssetsは、AssetBundle.Loadによってロードされたアセットをアンロードすることもできますが、対応するAssetBundleがUnload（false）をコールし、引用されていない場合に限ります。

---
**Q3：UGUIで作成したインターフェースに背景画像があり、ゲームで何も処理していません。Resources.UnloadUnusedAssetsはもうコールしましたが、次の図に示すように、インターフェースを閉じても画像はメモリに残っています。私のプリセットはAssetBundleにパッケージしなく、Resourceにロードします。これはなぜですか？**
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/2168d4d1-1472-6fad-06ca-e33cb1d3b310.png)
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/650a1d27-0621-ba2a-77fe-cb059e2282e9.png)
>Resources.Loadを使ってUIインターフェイスをロードする場合、「このインターフェイスを閉じて」した後でも、確かにResources.UnloadUnusedAssetsは対応するアトラスをアンロードできません。なぜなら、現時点で、このアトラスはまだResources.LoadがロードしたPrefab に引用されています。
この状況に対して、UWAからの推薦案はResources.UnloadAssetsを手動で呼び出して、アトラスを手動で解放することです（対応するアトラスは、Sprite.textureから見つけることができます）。UIインターフェイスが再インスタンス化されると、アトラスも自動的にReloadされます。

---
**Q4：Resources.UnloadAsset でInstanceしていないObject を解放すると下記のようなエラーが発生します。
Unload Assets may only be used on individual assets and can not be used on GameObject’s/Components or AssetBundles.
どうすれば解決できますか？**
>Resources.UnloadAssetはGameObjectやComponentではないアセットのみ解放でき、例えばTextureやMeshのような本格的なアセット。PrefabがロードしたObjectまたはComponentは、この関数で解放できません。

---
**Q5：ProfilerでGC.MarkDependenciesのCPUコストは900ms以上であることがわかります。戦闘終了時にResources.UnloadUnusedAssets（）;をコールしましたが、重い状況はまだ明らかです。何か推薦案ありませんか？**
>GC.MarkDependenciesのコストは、Resources.UnloadUnusedAssetsが引き起こしたものです。この関数の主な機能は、もう使用しないアセットを見つけてアンインストールすることです。ゲームシーンが複雑でアセットが多いほど、この関数のコストは高くなり、通常は300〜2000ミリ秒の範囲になります。

>GC.MarkDependenciesのコストは、Resources.UnloadUnusedAssetsが引き起こしたものです。この関数の主な機能は、もう使用しないアセットを見つけてアンインストールすることです。ゲームシーンが複雑でアセットが多いほど、この関数のコストは高くなり、通常は300〜2000msになります。そのため、関数のコール状況をより明確に了解するために、UWAレポートにコール監視機能を追加しました。
![7-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/cd88a852-68c1-793f-5ccc-23f4258812ce.png)
この関数の最適化に対して、一方ではシーンにある不要なアセットを制御すると同時に、Resources.UnloadUnusedAssetsの時間コストを減らすために、UnloadAssetを介してもう使わないアセットを早速アンロードすることをお勧めします。

>以後、UWAはローディングモジュールに関する記事にResources.UnloadUnusedAssetsのパフォーマンス問題を詳しく説明します、ご期待ください。

---
**Q6：一つのアトラスを使用するUIプリセットがあります。パッケージ化するときに、アトラスとUIを一緒にAssetBundleにパッケージ化しました。ロードしてGameObjectを生成した後、すぐにAssetBundle対象をアンロードしましたが、後で再びGameObjectを削減すると、アトラスがまだ存在していることがわかります。これはどうしてですか？**
>これは非常に可能性が高いです。undo（false）でAssetBundleをアンロードしても、ロードされたアセットは破棄されません。Resources.UnloadUnusedAssetsをコールする必要があります。

>AssetBundleローディングの詳細については、前の記事[「知っておくべきAssetBundle管理の原理」](https://qiita.com/UWATechnology/items/214030596cacbbceebdd)を参照してください。

---
**Q7：最初にPrefabをDestroyして、そしてPrefabに使うAssetBundleをUnloadします、このような手順に問題はありますか？携帯電話でテストしたところ、メモリは常に存在し、解放されないことがわかりました。逆にすると解放される可能性があります。さらに、Destroyの時にResources.UnloadUnusedAssets();をコールします、これは最終結果に影響ありませんか？**
>この状況は実際に発生する可能性があります。Resources.UnloadUnusedAssets();時に、AssetBunldeのUnload操作が実行されていない場合、AssetBunldeからロードされたアセットはAssetBundleに引用されたためにアンロードできません。開発チームはDestoryの後にAssetBunldeのUnloadを行い、最後にResources.UnloadUnusedAssets();を使用します。

---
##三、管理方式
**Q1: 現時点では、Unityはまだ NavMeshデータまたはLightmapデータからシーンを分離できないでしょう？最初に一つクリアなシーンをロードして、さまざまなライトマップとNavMeshメッシュデータに動的に切り入れたいですが、何か方法ありませんか？**
>Lightmapはシーンから分離でき、AssetBundleを介して動的にロードできます。Lightmapを一般的なアセットとしてパッケージ化し、動的にロードした後、LightmapSettingインターフェイスを介してシーンのLightmapを全体的に置き換えることをお勧めします。目前、Navmeshは確かにシーンから分離できませんが、Unity5.xバージョン以降、エンジンはLoadLevelAdditiveを介して複数のシーンをロードしてNavMeshをロードすることを許可しているため、開発チームは事前に複数のシーンでNavMeshをベイクしてから、LoadLevelAddtiveまたは同様のAPIを介してロードでき、NavMeshを動的にロードする効果を実現できます。

---
**Q2：アプリケーションをクリックしてからゲーム画面が表示されるまでの時間は、Resourceディレクトリのサイズに関係していますか？**
>アプリケーションをクリックしてからアプリケーション画面が最初に表示されるまで、ロード時間は主に2つの方面に関連しています。
1、Resourcesフォルダー内のアセットの数。ゲームが開始する時、Unityエンジンは、後続のアセットローディングの便利のために、Resourcesフォルダーに一つの検索ツリーを構築して対応するインデックスを格納します。一般的に、Resoucesフォルダーの下のリソースが多いほど、構築時間が長くなり、アプリケーションの起動が遅くなります。
2、最初シーンのアセットローディングと関連コードの初期化。最初シーンのアセットの量が多く、スクリプトの初期化のタスクが多い場合、アプリケーションの起動時間も遅くなります。

---
**Q3：AssetBundleが使用時に解凍されたアセットは、一定量のメモリを占有します。現在、2つのロード方法を試したいです。
（1）AssetBundleが関連するアセットをロードした後、アセットをキャッシュして、AssetBundleファイルをアンロードします。
（2）AssetBundleファイルをキャッシュし、後で関連するアセットを使用したい時にロードします。
この2つの方法のどちらが良いですか？**
>Unity 5.3より前のプロジェクトでは、LoadFromCacheOrDownloadまたはLoadFromFileを介してAssetBundleをロードすることをお勧めします。これで、Assetbundleオブジェクトのメモリ占有を減らすだけでなく、AssetBundleとアセット間のリンク関係を維持することで、依存関係のあるPrefabの後続のロードを容易にします。

>Unity 5.3以降のプロジェクトに対して、AssetBundleパッケージ自体から処理できます。つまり、デフォルトのLZMA形式ではなくLZ4形式のAssetBundleファイルを使用します。同様に上記の要件も満たすことができます。わずかな欠点は、LZ4形式のAssetBundleファイルがLZMA形式のファイルよりも少し大きいことです。

>依存関係パッケージの場合、依存される共有アセットのAssetBundleファイルをメモリにキャッシュすることをお勧めします。メモリはある程度増加しますが、上記の方法でメモリ使用量を大幅に削減し、メモリの負荷を軽減できます。

---
**Q4：Navmeshを動的にロードする方法は？**
>現在、Navmeshは動的ロードをサポートしておらず、シーンとともにのみロードできます。したがって、NavmeshのあるシーンをAssetBundleにパッケージして、LoadLevelを使用してAssetBundleにあるシーンをロードできます。

>Navmeshの動的ロードは、すでにUnityのRoadmapに含まれています。しかし、現在のNavmeshはシーンにバインドされています。つまり、今はLoadLevelによってのみロードでき（>> LoadLevelAdditiveのロードモードはサポートされていません）、対するNavmeshデータが同時に自動的にロードされます。替われる案は：複数の「シーンPrefab」のNavmeshを同じシーンに合併してベイクします（互いに重なり合うことなく）。そして、これらの「シーンPrefab」を個々のシーンに分離します。実行中には、Navmeshは初めなシーンに従って一度にロードします。他のシーンオブジェクトに対しては、LoadLevelAdditiveを介して対するシーンをロードすればいいです。欠点は、シーンオブジェクトをNavmeshに揃えるために、シーンの作成中に座標のオーバーラップは発生できません。ですから、ただの参照にしてください。

>Unity 5.xでは、Lightmapの動的ロードには、スクリプトでベイク中に各オブジェクトのLightmapindexとLightmapscaleoffsetを記録し、実行時の動的ロード後に元に設置し戻す必要があります。なぜなら、現在のLightmapindexおよびLightmapscaleoffset情報はシーンにバインドされ、Lightmapsnap.assetsに格納されるため、リリース時にシーン情報にも配置されるため、Prefabには記録されません。

---
**Q5：同じテクスチャに対してPrefabによって生成されたインスタンスが複数ある場合、このテクスチャのコピーは複数ありますか？**
>この質問では、Prefab の具体的なロード方法を確認する必要があります。Resources.Loadを介してのみロードされる場合、テクスチャには複数のコピーはありませんが、AssetBundleを介してロードされ、各 Prefabは一つのAssetBundle、そしてテクスチャは依存関係パッケージしていない場合、テクスチャアセットは確かにメモリに複数あります。

---
##四、Instantiateインスタンス化
**Q1：GameObject. Instantiateを初めて実行するとき、一部のアセットは少し重くなります（その時点でロードおよびインスタンスする）。ある複雑なアセットはGameObject. Instantiateを初めて実行すると70ms以上スタックし、明らかな重い状況が発生します。何かいい解決策はありませんか？**
>Instantiateの遅延は、関連あアセットのロード、スクリプトコンポーネントのシリアル化、およびコンストラクターの実行という三つのコストに関係あります。ほとんどの原因は関連あアセットのロードが導きます。だから、UWAからの推奨は次のとおりです。

>1.Profilerを介してInstantiate の具体的なCPU割り当てを確認します。

>2.アセットローディングが引き起こすパフォーマンスボトルネックである場合、アセットを単純化してCPUの時間コストプレッシャーを軽減しながら、一方でAssetBundle依存関係パッケージ化を介してアセットをプリロードします。つまり、ここでInstantiateの全体的な時間のかかる部分を分割し、均等に前のフレームに共有して実行します（シーンの切り替えなど）。これでインスタンス化操作の局部時間コストをスムーズになさせます。

>3.スクリプトコンポーネントのシリアル化によって引き起こされるパフォーマンスのボトルネックである場合は、スクリプト内のシリアル化情報を減らすことを試みることができます。

>4.コンストラクターの実行によって引き起こされるパフォーマンスのボトルネックである場合、通常は、インスタンス化のコール頻度を減らすなどの方法でのみ回避できます。

---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

UWA公式サイト：https://jp.uwa4d.com
UWA公式ブログ：https://blog.jp.uwa4d.com