AssetBundleの原則

##三、AssetBundleの原則

**AssetBundleシステムは、1つから複数のファイルにインデックスを付けてシリアル化できる圧縮ファイル形式を提供します。**
 
Unityプロジェクトがインストールされた後、コードを含まないアセットはAssetBundleを介して更新されます。 これにより、開発者は最初に小さなアプリケーションパッケージを送信して、ランタイムメモリの負荷を最小限に抑え、さまざまなエンドユーザーデバイスための最適化コンテンツを選択的にロードできます。
 
###3.1AssetBundleの仕組み

 
ざっくり言うと、AssetBundleは従来の圧縮パッケージと同様に、ヘッダーとデータ・セグメントの2つの部分からなっています。
 
ヘッダーには、識別子、圧縮タイプ、コンテンツマニフェストなどのAssetBundleに関する情報が含まれています。マニフェストは、Objects nameをキーとするルックアップテーブルです。各エントリは、AssetBundleデータセグメント内のObjectsの位置を示すバイトインデックスを提供します。ほとんどのプラットフォームでは、このルックアップテーブルはバランスの取れた探索木を使用して実装されます。具体的には、WindowsとOSXから派生したプラットフォーム（iOSを含む）はどちらも赤黒木を使用します。したがって、マニフェストの構築に必要な時間は、AssetBundle内のAssetsの数が増えるにつれて直線的に増加します。
 
データセグメントには、AssetBundle内のAssetsをシリアル化することによって生成された元のデータが含まれます。圧縮スキームとしてLZMAが指定されている場合、すべてのシリアル化されたAssetsの完全なバイト配列が圧縮されます。 LZ4が指定されている場合、個々のAssetsのバイトは個別に圧縮されます。圧縮を使用しない場合、データセグメントは元のバイトストリームのままになります。
 
Unity 5.3より前は、AssetBundle内の個々のObjectsを圧縮することはできませんでした。したがって、5.3より前のバージョンのUnityで圧縮されたAssetBundleから1つ以上のオブジェクトを読み取る必要がある場合、UnityはAssetBundle全体を解凍する必要があります。通常、UnityはAssetBundleの解凍されたコピーをキャッシュして、同じAssetBundleでの後続のロードリクエストのロードパフォーマンスを向上させます。
 
###3.2AssetBundleをロードする

 
AssetBundlesは、4つの異なるAPIを介してロードできます。ただし、以下のような2つの基準によって制限されているため、これら4つのAPIの動作は異なります。
 
2つの基準は次のとおりです。
>AssetBundlesの圧縮方法：LZMA、LZ4、または非圧縮。
AssetBundleの読み込みプラットフォーム。
 
4つのAPIは次のとおりです。
>AssetBundle.LoadFromMemory(Async optional)
AssetBundle.LoadFromFile(Async optional)
UnityWebRequest’s DownloadHandlerAssetBundle
WWW.LoadFromCacheOrDownload (on Unity 5.6 or older)
 
以下では、4つのAPIの違いについて詳しく説明します。
 
####3.2.1 AssetBundle.LoadFromMemory（Async）

 
UnityからのアドバイスはーーこのAPIを使用しないでください。
 
LoadFromMemory（Async）は、マネージコードのバイト配列からAssetBundleをロードします。つまり、他の方法で事前にリソースのバイナリ配列をメモリに追加する必要があります。次に、インターフェイスは、ソースデータをマネージコードバイト配列から、新しく割り当てられ、連続しているネイティブメモリのブロックにコピーします。
 
ただし、AssetBundleがLZMA圧縮タイプを使用している場合は、コピー時にAssetBundleを解凍します。非圧縮およびLZ4圧縮のAssetBundleは、バイトごとにコピーされます。
 
このAPIが推奨されない理由は、このAPIによって消費されるメモリの最大量がAssetBundleの少なくとも2倍になるためです。つまり、ネイティブメモリのコピーと、LoadFromMemory（Async）がマネージバイト配列からコピーしたコピーです。
 
したがって、このAPIを介して作成されたAssetBundleからロードされたアセットは、メモリ内で3回冗長になります。1回目はマネージドコードバイト配列で、2回目はAssetBundleのスタックメモリのコピーで、3回目はAsset自体に用いられるGPUまたはシステムメモリで冗長になります。
 
注：Unity 5.3.3より前は、このAPIはAssetBundle.CreateFromMemoryと呼ばれていたが機能は変わっていません。
 
####3.2.2 AssetBundle.LoadFromFile（Async）

 
LoadFromFileとは、一種の効率的なAPIです。ローカルストレージ（ハードドライブやSDカードなど）から、非圧縮またはLZ4圧縮形式のAssetBundleをロードするために用いられます。
 
デスクトップスタンドアロンプラットフォーム、コンソール、およびモバイルプラットフォームでは、APIはAssetBundleのヘッダーのみをロードし、残りのデータをディスクに残します。
 
AssetBundleのObjectsは必要に応じてロードできす。例えば、ロードメソッド（例：AssetBundle.Load）が呼び出されたときや、そのInstanceIDが間接的に参照されたときなど、あまり多くのメモリが使用されることはありません。
 
ただし、Editor環境では、APIはAssetBundle全体をメモリにロードします。まるでディスク上のバイトを読み取ってAssetBundle.LoadFromMemoryAsyncを使用するのと同じようです。
プロジェクトがEditorで分析される場合、このAPIにより、AssetBundleのロード中にメモリスパイクが発生する可能性があります。ただし、これはデバイスのパフォーマンスに影響を与えることはありません。最適化の前に、これらのスパイクをデバイスで再度テストする必要があります。
 
このAPIは、非圧縮またはLZ4圧縮形式専用であることに注意してください。前述のように、LZMA圧縮を使用すると、生成されたデータパケット全体が圧縮されるため、解凍前にAssetBundleのヘッダー情報を取得できません。 
 
注：ここには従来の問題が残されてきました。つまり、Unity 5.3以前のAndroidデバイスでStreaming AssetsパスからAssetBundleを読み込もうとすると、このAPIは失敗します。この問題はUnity5.4で解決されています。
 
Unity 5.3より前は、このAPIはAssetBundle.CreateFromFileと呼ばれていました。その機能は変わっていません。
 
####3.2.3 AssetBundleDownloadHandler

 
DownloadHandlerAssetBundleの操作は、UnityWebRequestのAPIを介して行われます。
 
UnityWebRequest APIを使用すると、開発者はUnityがダウンロードしたデータを処理する方法を正確に指定でき、不要なメモリ使用量を排除できます。UnityWebRequestを使用してAssetBundleをダウンロードする最も簡単な方法は、UnityWebRequest.GetAssetBundleを呼び出すことです。
 
実際のプロジェクトにとって、最も興味深いクラスはDownloadHandlerAssetBundleです。それはワーカースレッドを使用して、ダウンロードしたデータストリームを固定サイズのバッファーに格納し、ダウンロード・処理プログラムの配置方法に従って、バッファーデータを一時ストレージまたはAssetBundleキャッシュに配置します。
 
これらの操作はすべてアンマネージコードで行われるため、monoメモリが増えるリスクがなくなります。さらに、このダウンロード・処理プログラムは、ダウンロードされたすべてのバイトのスタックメモリのコピーを保持しないため、AssetBundleのダウンロードのメモリ使用がさらに削減されます。
 
LZMAによって圧縮されたAssetBundleは、ダウンロードおよびキャッシュ時にLZ4圧縮に変更されます。これは、Caching.CompressionEnableプロパティを設定することで変更できます。
 
キャッシュ情報がUnityWebRequestオブジェクトに提供されている場合、要求AssetBundleがUnityキャッシュにすでに存在すると、AssetBundleはすぐに使用可能になり、このAPIの動作はAssetBundle.LoadFromFile操作と同じになります。
 
Unity 5.6より前のUnityWebRequestシステムは、固定ワーカースレッドプールと内部オペレーティングシステムを使用して、過剰な同時ダウンロードを防止し、スレッドプールのサイズを構成できませんでした。Unity 5.6では、これらのセキュリティ対策が削除され、最新のハードウェアに対応し、HTTP応答コードとヘッダーへのより高速なアクセスが可能になりました。
 
####3.2.4 WWW.LoadFromCacheOrDownload

 
これは非常に古いAPIです。Unity2017.1以降、UnityWebRequestをラップするだけです。したがって、Unity 2017.1以降を使用している開発者は、UnityWebRequestを直接使用して作業する必要があります。 Unityは変更されたインターフェースのメンテナンスを放棄し、将来のバージョンで削除する可能性があります。
 
そのため、以下のコンテンツはUnity5.6以前のバージョンにのみ適しています。
 
WWW.LoadFromCacheOrDownloadを使用すると、リモートサーバーおよびローカルストレージからオブジェクトをロードできます。ファイル// URLを介してローカルストレージからファイルをロードすることもできます。 AssetBundleがUnityCacheに存在する場合、このAPIの動作はAssetBundle.LoadFromFileとまったく同じになります。
 
AssetBundleがキャッシュされていない場合、WWW.LoadFromCacheOrDownloadはそのソースファイルからAssetBundleを読み取ります。 AssetBundleが圧縮されている場合、ワーカースレッドを使用して解凍され、キャッシュに書き込まれます。それ以外の場合は、ワーカースレッドを介してキャッシュに直接書き込まれます。
 
AssetBundleがキャッシュされた後、WWW.LoadFromCacheOrDownloadは、キャッシュされ、解凍されたAssetBundleからヘッダー情報をロードします。そレから、AssetBundle.LoadFromFile がAssetBundleをロードすると同じような動作を行います。
 
このキャッシュは、WWW.LoadFromCacheOrDownloadとUnityWebRequestの間で共有されます。あるAPIからダウンロードされたAssetBundleは、別のAPIを介して取得することもできます。
 
データは固定サイズのバッファを介して解凍され、キャッシュに書き込まれますが、WWWオブジェクトはAssetBundleバイトの完全なコピーをローカルメモリに保持します。この余分なコピーが保持される理由は、WWW.bytesバイト属性をサポートするためです。
 
WWWオブジェクトにAssetBundleのバイトをキャッシュすることによるメモリのコストを削減するために、実際のプロジェクト開発ではAssetBundleを小さくにする必要があります。
 
UnityWebRequestとは異なり、このAPIを呼び出すたびに新しいワーカースレッドが生成されます。したがって、携帯電話などのメモリが制限されているプラットフォームでは、メモリの急増を避けるために、一度に1つのAssetBundleのダウンロードのみを制限するのが最善です。他のプラットフォームでは、作成するスレッドが多すぎるように注意してください。5つを超えるAssetBundlesをダウンロードする必要がある場合は、スクリプトコードでダウンロードキューを作成および管理して、同時に少数のAssetBundleのみがダウンロードされるようにすることをお勧めします。
 
####3.2.5推奨事項

 
（1）一般的に、AssetBundle.LoadFromFileは可能な限り使用してください。このAPIは、速度、ディスク使用量、およびランタイムメモリ使用量の点で最も効果的です。
（2）AssetBundleをダウンロードまたはホットアップデートする必要があるプロジェクトの場合、Unity 5.3以降を使用するプロジェクトにはUnityWebRequestを使用し、Unity5.2以前を使用するプロジェクトにはWWW.LoadFromCacheOrDownloadを使用することを強くお勧めします。
（3）UnityWebRequestまたはWWW.LoadFromCacheOrDownloadを使用する場合、AssetBundleのロード後にダウンロードプログラムコードがDisposeを正しく呼び出すことを確認してください。 さらに、C＃usingステートメントは、WWWまたはUnityWebRequestが安全に処理されることを保証するための最も便利な方法です。
（4）独特な特定のキャッシュ・ダウンロード要件を必要とする大規模なプロジェクトの場合、カスタムダウンローダーの使用を検討できます。 カスタムダウンロードプログラムの作成は重要で複雑な作業です。カスタムダウンロードプログラムは、AssetBundle.LoadFromFileと互換性がある必要があります。
 
###3.3AssetBundleからアセットをロードする

 
ここまで、AssetBundleの取得はできるようになりました。次はAssetBundleからAssetsを取得します。
 
Unityは、AssetBundlesからUnityEngine.Objectsをロードするための3つの異なるAPIを提供します。これらのAPIはAssetBundleオブジェクトにバインドされており、これらのAPIには同期および非同期のバリアントがあります。
 
>（1）LoadAsset（LoadAssetAsync）
（2）LoadAllAssets（LoadAllAssetsAsync）
（3）LoadAssetWithSubAssets（LoadAssetWithSubAssetsAsync）
 
また、これらのAPIの同期バージョンは、非同期バージョンよりも常に少なくとも1フレーム高速です（実際、非同期バージョンは非同期を確保するために少なくとも1フレーム遅延するため）。非同期ロードは、タイムスライスが切り取られるまでに、フレームごとに複数のオブジェクトがロードされます。
 
複数の独立したUnityEngine.Objectsをロード場合は、LoadAllAssetを使用すべきです。しかもAssetBundle内のほとんどまたはすべてのオブジェクトをロードする必要がある場合にのみ使用します。他の2つのAPIと比較すると、LoadAllAssetは、LoadAssetへの複数の個別の呼び出しよりもわずかに高速です。したがって、ロードするAssetの数が多いが、一度にロードする必要があるのがAssetBundleの3分の2未満の場合は、AssetBundleを複数の小さなパッケージに分割してから、LoadAllAssetを使用することを検討してください。
 
複数の埋め込みオブジェクトを含む複合Assetをロードする場合は、LoadAssetWithSubAssetを使用すべきです。例えば、アニメーションが埋め込まれたFBXモデルや、複数のスプライトが埋め込まれたspriteアトラスなどのような場合。つまり、ロードする必要のあるオブジェクトがすべて同じAssetからのものであるが、他の多くの無関係なオブジェクトと一緒にAssetBundleに保存されている場合は、このAPIを使用します。
 
それ以外の場合は、LoadAssetまたはLoadAssetAsyncを使用してください。

####3.3.1低レベルの読み込みの詳細

 
Objectのロードはメインスレッドで実行されますが、データはワーカースレッドのストレージから読み取られます。Unityシステムのスレッドに敏感な部分（スクリプト、グラフィックス）に触れない作業は、ワーカースレッドで変換されます。たとえば、VBOはメッシュから作成され、テクスチャは解凍されます。
 
Unity 5.3以降、Objectの読み込みは並列化されています。ワーカースレッドで複数のオブジェクトを逆シリアル化し、処理し、統合します。Objectの読み込みが完了すると、そのAwakeコールバックが呼び出され、オブジェクトの残りの部分が次のフレームでUnityEngineで使用できるようになります。
 
AssetBundle.Loadメソッドを同期すると、Objectがロードされるまでメインスレッドが一時停止されます。ただし、Objectの統合にミリ秒のフレーム時間がかかりすぎないように、タイムスライスのObjectも読み込まれます。アプリケーションプロパティには、ミリ秒数のプロパティは、Application.backgroundLoadingPriorityを設定します。
>ThreadPriority.High：フレームあたり最大50ミリ秒
ThreadPriority.Normal：フレームあたり最大10ミリ秒
ThreadPriority.BelowNormal：フレームあたり最大4ミリ秒
ThreadPriority.Low：フレームあたり最大2ミリ秒
 
Unity 5.2以降、複数のオブジェクトがロードされると、オブジェクトのロードのフレーム時間制限に達するまで続行されます。他のすべての要素が等しいと仮定すると、非同期呼び出しとオブジェクトの間に最小1フレームの遅延があるため、Asset がAPIの非同期バリアントの読み込みは常に同期バージョンよりも時間がかかります。

####3.3.2AssetBundleの依存関係

 
ランタイム環境に応じて、2つの異なるAPIを使用して、AssetBundle間の依存関係を自動的に追跡します。 UnityEditorでは、AssetDatabaseAPIを介してAssetBundleの依存関係を照会できます。 AssetBundleの割り当てと依存関係は、AssetImportAPIを介してアクセスおよび変更できます。 Unityは、実行時に、ScriptableObjectに基づくAssetBundleManifest APIを介して、AssetBundle構築プロセス中に生成された依存関係情報をロードするためのオプションのAPIを提供します。
 
1つ以上のAssetBundleのUnityEngine.Objectsが1つ以上の他のAssetBundleのUnityEngine.Objectsを参照する場合、このAssetBundleは別のAssetBundleに依存します。 AssetBundleは、含まれる各オブジェクトのFileGUIDとLocalIDによって識別されるソースデータとして機能します。
 
インスタンスIDが初めて間接的に参照されるときにオブジェクトがロードされ、AssetBundleがロードされるときにオブジェクトに有効なインスタンスIDが割り当てられるため、AssetBundleがロードされる順序は重要ではありません。代わりに、オブジェクト自体をロードする前に、オブジェクトの依存関係を含むすべてのAssetBundleをロードすることが重要です。親AssetBundleがロードされるときに、Unityは子AssetBundleを自動的にロードしようとはしません。
 
例：
MateriaAがTextureBを参照しているとします。MateriaAはAssetBundle1にパッケージ化され、TextureBはAssetBundle2にパッケージ化されます。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653855/92dc13b6-abc5-ba71-2c78-cdff6f9b306c.png)


 
この例では、AssetBundle1からMaterialAをロードする前に、AssetBundle2をロードしなければなりません。これは、AssetBundle2をAssetBundle1の前にロードしなければならないこと、またはTextureBをAssetBundle2から明示的にロードしなければならないことを意味するものではありません。 AssetBundle 1からMaterial Aをロードする前に、AssetBundle2をロードするだけで十分です。
 
簡単に言えば、AssetBundleのロード間に順はありませんが、Assetsのロードには順があります。
 
AssetBundleの依存関係の詳細については、[「マニュアル」](https://docs.unity3d.com/Manual/AssetBundles-Dependencies.html?_ga=2.168691873.1408835506.1571651191-1030292064.1564583003)を参照してください。

####3.3.3AssetBundle manifests

 
BuildPiine.BuildAssetBundles APIを使用してAssetBundleビルドパイプラインを実行する場合、Unityは各AssetBundleの依存関係情報を含むオブジェクトをシリアル化します。このデータは、AssetBundleManifestタイプの単一のオブジェクトを含む別のAssetBundleに格納されます。
 
このAssetは、AssetBundlesが構築されている親ディレクトリと同じ名前のAssetBundleに保存されます。プロジェクトがそのAssetBundlesを（Projectroot）/ Build / Client /にあるフォルダーにビルドする場合、マニフェストを含むAssetBundleは（Projectroot）/build/client/Client.manifestとして保存されます。
 
Manifestを含むAssetBundleは、他のAssetBundleと同じようにロード、キャッシュ、およびアンロードできます。
 
AssetBundleManifestオブジェクト自体は、マニフェストと同時に構築されたすべてのAssetBundleを一覧表示するGetAllAssetBundles APIと、特定のAssetBundleの依存関係を照会する2つのメソッドを提供します。
 
>（1）AssetBundleManifest.GetAllDependenciesは、AssetBundleの直接の子とその子の依存関係を含む、AssetBundleのすべての階層的な依存関係を返します。
（2）AssetBundleManifest.GetDirectDependationsは、AssetBundleの直接の子レベルのみを返します
これらの2つのAPIは文字列配列を割り当てることに注意してください。したがって、パフォーマンス要件に敏感でない場合に使用するのが最適です。

####3.3.4推奨事項

 
ほとんどの場合、プレーヤーがアプリケーションのパフォーマンスが重要な領域（メインのゲームレベルやワールドなど）に入る前に、できるだけ多くの必要なオブジェクトをロードするのが最善です。モバイルプラットフォームではローカルストレージへのアクセスが遅く、実行時にオブジェクトをロードおよびアンロードするとガベージコレクションがトリガーされるため、これはモバイルプラットフォームでは特に重要です。
 
---
UWA Technologyは、モバイル/VRなど様々なゲーム開発者向け、**パフォーマンス分析**と**最適化ソリューション**及び**コンサルティングサービス**を提供している会社でございます。

今なら、UWA GOTローカルツールが**15日間に無償試用**できます！！
よければ、ぜひ！

UWA公式サイト：https://jp.uwa4d.com
UWA GOT OnlineレポートDemo：https://jp.uwa4d.com/u/got/demo.html
UWA公式ブログ：https://blog.jp.uwa4d.com