Unity を検索

アセットバンドルの使用方法を改善してメモリ消費を抑えよう

2020年4月9日 カテゴリ: Engine & platform | 7 分 で読めます
シェア

Is this article helpful for you?

Thank you for your feedback!

あなたのアプリケーションが、アセットをコンテンツデリバリネットワーク(CDN)からストリーミングしているか、全てのアセットを 1 つの大きなバイナリにまとめているかに関わらず、あなたはきっと「アセットバンドル」という言葉をお聞きになったことがあるでしょう。 アセットバンドルは、(1 つまたは複数の)シリアライズされたアセット(テクスチャ、メッシュ、オーディオクリップ、シェーダーなど)を含んだ、ランタイムで読み込めるファイルです。

アセットバンドルは、直接あるいは Unity の Addressable Asset System(Addressables)などのシステム経由で使用することができます。Addressables システムは、プロジェクト内でアセットを管理するためのよりサポートされた使いやすい方法を提供するパッケージで、アセットバンドルを抽象化によって補足するものです。Addressables は開発者によるアセットバンドルの直接操作を最小限に抑えますが、アセットバンドルの使用がメモリ消費にどのように影響するかを理解しておくことは有用です。Addressables システムの概要は、こちらのブログ記事 Unite Copenhagen 2019 のこちらのセッションをご覧ください。

新しいプロジェクトを開始される開発者の皆様は、アセットバンドルを直接操作する代わりに Addressables を使用されることをぜひご検討ください。アセットバンドルの扱い方がすでに確立された状態でプロジェクトを進行中の皆様は、本記事内で触れている、アセットバンドルがランタイムメモリに与える影響についての情報が、最高の結果を得るための一助となるでしょう。

メモリキャッシュによるメモリ消費

Unity は、LZMA アセットバンドルを(現在は非推奨化されている)WWW クラスあるいは UnityWebRequestAssetBundle(UWR)を使用してダウンロードする際、メモリキャッシュとディスクキャッシュの 2 つのキャッシュを使用してアセットバンドルのフェッチング・再圧縮・バージョニングを最適化します。

メモリキャッシュ内に読み込まれたアセットバンドルは大量のメモリを消費します。特定のアセットバンドルのコンテンツに素早く頻繁にアクセスしたい場合を除いては、メモリキャッシュを使用すると消費メモリ量が大きくなり過ぎる傾向にあるため、代わりにディスクキャッシュを使用することが推奨されます。

バージョンあるいはハッシュ引数を UnityWebRequestAssetBundle API に提供すると、Unity はアセットバンドルデータをディスクキャッシュ内に保存します。これらの引数を提供しなければ Unity はメモリキャッシュを使用します。Addressables はデフォルトではディスクキャッシュを使用します。この挙動は UseAssetBundleCache フィールドから制御することができます。

AssetBundle.LoadFromFile()AssetBundle.LoadFromFileAsync() は LZMA アセットバンドルに常にメモリキャッシュを使用します。したがって、代わりに UnityWebRequestAssetBundle API をご使用になることをお勧めします。UnityWebRequestAssetBundle API が使用できない場合は、AssetBundle.RecompressAssetBundleAsync() を使用して LZMA アセットバンドルをディスク上で書き換えることも可能です。

社内テストの結果、ディスクキャッシュを使用した場合とメモリキャッシュを使用した場合の RAM の使用量に少なくとも 1 桁の違いがあることが分かっています。アプリケーションごとに、メモリの消費量と、追加で必要になるディスク容量およびアセットのインストール時間とのバランスを考える必要があります。

アセットバンドルのメモリキャッシュがアプリケーションのメモリの使用状況にどのように影響しているかを把握するには、ネイティブのプロファイラー(あるいは Xcode の Allocations Instrument)ツールを使用して ArchiveStorageConverter クラスからの割り当てを調査してください。このクラスが 10MB を超える量の RAM を使用している場合は、恐らくメモリキャッシュが使用されています。

Xcode の Allocations Instruments ― ArchiveStorageConverter クラスの使用しているメモリの量が表示されています。

アセットバンドルが具体的に何を読み込んでいるか確認する

大きなプロジェクト向けにアセットバンドルをビルドする場合、Unity のデフォルトでは、そこに含まれる重複した情報の最小化は実行されません。生成されたアセットバンドル内の重複データのインスタンスを特定するには、Unity の Consulting & Development グループのメンバーが Python で記述した AssetBundle Analyzer が便利です。このツールをコマンドラインから使用すると、生成されたアセットバンドルから情報が抽出され、便利なビュー機能を備えた SQLite データベース内に格納されます。このデータベースは DB Browser for SQLite などのツールを使用してクエリすることができます。このツールは、(バンドルを手動で作成した場合でも Addressables 経由で作成した場合でも)ビルドパイプライン内の非効率性を見付けて解決します。

AssetBundle Analyzer ツールの結果を DB Browser for SQLite を使用して分析する

この他に、AssetBundle Browser ツールダウンロード可能となっています(プロジェクト内にそのまま統合できます)。このツールは Addressables と類似した機能を提供しますので、Addressables をご使用の場合は、このツールをご使用になる必要はありません。

AssetBundle Browser ツールは、特定の Unity プロジェクト内のアセットバンドルの設定を閲覧・編集できる機能だけでなく、ビルド機能も提供します。その他にも、依存が原因でプルされている重複アセット([例]テクスチャ)の通知など、便利な機能がいくつか搭載されています。

AssetBundle Browser ツールは、(複数のバンドルによってプルされている)依存に起因した重複に関する警告を表示します。

重複アセットによるメモリ消費

アセットをどのようにアセットバンドルにまとめるかを決める際には、依存関係に注意しなければなりません。アセットバンドルのトポロジーの如何に関わらず、Unity は、アプリケーションバイナリ内(Resources フォルダー内あるいはその関連フォルダー内)のアセットと、アセットバンドルから読み込む必要があるアセットとを区別します。これら 2 種類のアセットは、それぞれ別の世界に存在していると考えることができます。Resources フォルダーの世界に存在しているアセットのインスタンスへの強参照を持つアセットバンドルを作成することは不可能です。そうしたアセットを参照する場合、Unity は、その複製を作成し、それをアセットバンドルの世界で使用します。

2 つのコンポーネントが「logo」画像への強参照を持っています。これらのコンポーネントが(プレイヤーに関連付けらるか特定のアセットバンドルに含められる形で)異なるアーカイブ中にシリアライズされた場合、それぞれに、この画像の独自のコピーが含まれることになります。

例えば、ゲームのロゴを考えてみましょう。ロゴはゲーム開始時のローディングシーンの UI 内に表示されるとします。このローディングシーンは、リモートアセットがディスクにストリーミングされる前に表示される必要があるので、すぐに使用できるようにロゴアセットをビルドに含めるとします。

この同じロゴは、UI のオプションパネル(ユーザーが言語選択やサウンドなどの設定を行う場所)にも使用されます。この UI パネルがアセットバンドルから読み込まれるのであれば、そのアセットバンドルはロゴアセットの独自のコピーを作成します。

ローディングシーンとオプションパネルの両方が同時に読み込まれる場合、ロゴアセットの両方のコピーが読み込まれ、これが重複となってメモリを消費します。

この問題は、片方または両方のシーンのハードリンクを破壊することで解決されます。ロゴがアセットバンドル内に存在する場合は、アセットへの参照に先駆けて一部のストリーミングが行われなければなりません。ロゴがバイナリ内(例えば Resources フォルダー内など)に存在する場合は、UI パネルは、ロゴアセットへの弱参照を持たなければならず、Resources.Load などの API 経由で読み込まれる必要があります。

ロゴ画像への弱参照としてストリングが使用されています。ユーザースクリプトは画像をランタイムで読み込んで適切なコンポーネントにアサインするためにこのストリングを使用する必要があります。

ユーザースクリプトは画像をランタイムで読み込んで適切なコンポーネントにアサインするためにストリングを使用する必要があります。ロゴアセットを含むアセットバンドルをアプリケーションの StreamingAssets ディレクトリ内に含めるのも丁度良い方法です。この場合もアセットバンドルからアセットを読み込む必要はありますが、バンドルをローカルでホスティングしているので、コンテンツのダウンロードにコストが掛かりません。

アセットの参照にストリングやパスや GUID を使用するのは直感的ではありませんが、Unity のデフォルトのドラッグアンドドロップ参照機能を可能にするカスタムのインスペクターを、弱参照されたフィールドに作成すると良いかもしれません。また、Unity の MemoryProfiler パッケージを使用して、メモリ内で重複しているアセットを特定することもお忘れなく。Addressables システムには、依存の重複をチェックするための独自のメカニズムが備わっています(詳細はドキュメンテーションのこちらのページをご覧ください)。

まとめ

Addressables システムはアセットバンドルを抽象化によって補足しますが、内部的な仕組みを理解しておくことで、上記でご紹介したようなコストの高いパフォーマンス問題を回避することができます。

今後もこれに関連したブログ記事をシリーズで公開する予定です。特に取り上げてほしいトピックがございましたら、ぜひコメントをお寄せください!

2020年4月9日 カテゴリ: Engine & platform | 7 分 で読めます

Is this article helpful for you?

Thank you for your feedback!