Unity を検索

最適化の最前線から:Addressables を使ってメモリを節約する

2021年3月31日 カテゴリ: テクノロジー | 12 分 で読めます
A silver/grey sword is on the left, a pink sword is in the middle and a wooden/brown and silver/grey circular shield is on the right. The background is black.
A silver/grey sword is on the left, a pink sword is in the middle and a wooden/brown and silver/grey circular shield is on the right. The background is black.
取り上げているトピック
シェア

Is this article helpful for you?

Thank you for your feedback!

メモリ周辺でアセットの効率的にストリーミングさせることは、どのようなクオリティのゲームにおいても重要な要素です。私はプロフェッショナルサービスチームのコンサルタントとして、多くの顧客のプロジェクトのパフォーマンスの向上に努めてきました。その経験をもとに、Unity の Addressable アセットシステムを活用してコンテンツ読み込み時の戦略を改善する方法に関するヒントをいくつかご紹介したいと思います。

メモリは注意深く管理しなければならない希少なリソースであり、プロジェクトを新しいプラットフォームに移植するときには特に気を配る必要があります。Addressables を使用すると、弱参照を利用して不要なアセットが読み込まれないようにして、実行時のメモリの使用効率を向上させることができます。弱参照を使うと、参照されるアセットがメモリに読み込まれるタイミングとメモリから削除されるタイミングを制御することができます。Addressables システムは、必要なすべての依存関係を見つけてそれらを読み込むこともします。 このブログでは、Unity の Addressable アセットシステムを使うようにプロジェクトを設定する際、発生する可能性のあるいくつかのシナリオと問題について説明し、それらを認識して迅速に修正する方法について説明します。

ゲーム内の持ち物を扱うサンプル

Left is a siver/grey sword, middle is a pink and gold/yellow sword, right is a wood/brown and silver/grey circular shield. 'Using addressables' is at the bottom right of the image with options such as 'spawn sword' and spawn shield' visible.

これからご説明する一連の推奨事項を説明するにあたり、以下のような設定の単純なサンプルを用います。

  • シーンには、Sword、Boss Sword、Shield プレハブの 3 つの持ち物のアセットへの参照を含む InventoryManager スクリプトがある
  • これらのアセットは、ゲームプレイ中に常に必要なわけではない

このサンプルのプロジェクトファイルは、筆者個人の GitHub レポジトリからダウンロードできます。 実行時のメモリ使用状況を表示するために、プレビュー版の Memory Profiler パッケージを使用しています。Unity 2020 LTS では、パッケージマネージャーからこのパッケージをインストールする前に、まずプロジェクト設定でプレビュー版パッケージを有効にする必要があります。

Unity 2021.1 を使用している場合は、パッケージマネージャーウィンドウの追加メニュー(+ ボタン)から Add package by name オプションを選択します。そこで「com.unity.memoryprofiler」という名前を入力してください。

ステージ 1:強参照、Addressables なし

最も基本的な実装から始めて、Addressables コンテンツを設定するための最良のアプローチに近づけていきましょう。最初はシーンに存在する MonoBehaviour のプレハブに強参照(インスペクターでの直接割り当て、GUID によって追跡)を適用しただけのシンプルな設定です。

Screenshot of unity element editor menu with the options 'sword', 'Boss Sword' and 'Sheild'.

シーンが読み込まれると、シーン内のすべてのオブジェクトがそれらの依存関係とともにメモリに読み込まれます。これは、InventorySystem にリストされているすべてのプレハブが、それらのプレハブのすべての依存関係(テクスチャ、メッシュ、オーディオなど)とともにメモリに存在することを意味します。

ビルドを作成し、メモリプロファイラーを使用してスナップショットを作成すると、アセットのテクスチャがインスタンス化されていなくても、アセットのテクスチャがすでにメモリに保存されていることがわかります。

Screenshot showing different types of textures
アイテムのテクスチャが、まだインスタンス化されていないのにメモリに保存されている。

問題:現在必要のないアセットがメモリにある。持ち物になるアイテムが多数あるプロジェクトでは、これが実行時のメモリをひどく圧迫する原因になる。

ステージ 2:Addressables を実装する

不要なアセットの読み込みを回避するために、Addressables を使用するように持ち物システムを変更します。直接参照の代わりにアセット参照を使用すると、これらのオブジェクトがシーンと一緒に読み込まれるのを防ぐことができます。持ち物のプレハブを Addressables グループに移動し、InventorySystem を変更して、Addressables の API を使用してオブジェクトをインスタンス化および破棄します。

Assets menu with 'sword', 'bosssword', and 'shield' as options

プレイヤーをビルドし、スナップショットを取ります。アセットがまだメモリに読み込まれていないことに注意してください。これで、インスタンス化されていないならメモリに読み込まれないようになったということです。

Font textures in memory
持ち物になるアイテムのテクスチャはメモリに表示されず、TextMeshPro テクスチャのみが表示されている。

すべてのアイテムをインスタンス化して、アセットがメモリ内にある状態で正しく表示されることを確認します。

Left is a silver sword and right is a bronze and wooden shield.

問題:すべてのアイテムをインスタンス化して Boss Sword を破棄した時、使用されていないはずの Boss Sword のテクスチャ「BossSword_E」がメモリに表示されている。これが起きる原因は、アセットバンドルを部分的に読み込むことはできるが、自動的かつ部分的に破棄することはできないことである。このような挙動は、すべての持ち物のプレハブを含む単一のアセットバンドルなど、多くのアセットが含まれるバンドルを使う時、特に問題になる可能性がある。アセットバンドル全体が不要になるまで、またはコストのかかる CPU 命令である Resources.UnloadUnusedAssets() を呼び出すまで、バンドル内のどのアセットも破棄されない。

Sword and Shield textures
テクスチャ BossSword_E は、Boss Sword が破棄された後もメモリに残っている。
Data Layout and Addressable groups

ステージ 3:バンドルを小さくする

この問題を修正するには、アセットバンドルの整理方法を変える必要があります。現在、すべてのアセットを 1 つのアセットバンドルにパックする Addressables グループが 1 つだけありますが、プレハブごとにアセットバンドルを作成することもできます。細かくアセットバンドル化していくことで、大きなバンドルを使っていると不要になったアセットまでメモリに残ってしまう問題を軽減できます。

この変更は簡単です。Addressables グループを選択し、Content Packaging & Loading > Advanced Options > Bundle Mode を選択し、インスペクターに移動して、Bundle Mode を Pack Together から Pack Separately に変更します。

Pack Separately を使用してこの Addressables グループを構築することにより、Addressables グループ内のアセットごとにアセットバンドルを作成できます。

Adressables asset group menu

アセットとバンドルは次のようになります。

 

Assets and asset bundles

ここで、元のテストに戻ります。3 つのアイテムをスポーンしてから、Boss Sword を破棄しても、不要なアセットがメモリに残ることはなくなりました。バンドル全体が不要になったため、Boss Sword のテクスチャが破棄されるようになりました。

問題:3 つすべてのアイテムをスポーンし、メモリキャプチャを取得すると、重複するアセットがメモリに表示される。具体的には、テクスチャ「Sword_N」と「Sword_D」の複数のコピーが作成されている。バンドルの数だけを変更したとして、これはどのように発生するのだろうか。

Textures with duplicates

ステージ 4:重複するアセットを修正する

この問いに答えるために、私たちが作成した 3 つのバンドルに含まれるすべてのものについて考えてみましょう。3 つのプレハブアセットのみをバンドルに入れましたが、プレハブの依存関係として暗黙的にそれらのバンドルに取り込まれる追加のアセットがあります。たとえば、Sword のプレハブアセットには、含める必要のあるメッシュ、マテリアル、テクスチャのアセットもあります。これらの依存先が Addressables の他の場所に明示的に含まれていない場合、それらを必要とする各バンドルに自動的に追加されます。

Asset Bundles for the Sword and BossSword contain some of the same dependencies.
Sword と Boss Sword のアセットバンドルには、同じ依存関係がいくつか含まれている。

Addressables には、バンドルのレイアウトの診断に役立つ分析ウィンドウがあります。メニューを Window > Asset Management > Addressables > Analyze と開き、ルール Bundle Layout Preview を実行します。ここでは、Sword バンドルに sword.prefab が明示的に含まれていることがわかりますが、このバンドルには多くの暗黙的な依存関係も含まれています。

Rules menu

同じウィンドウで、Check Duplicate Bundle Dependencies を実行します。このルールは、現在の Addressables レイアウトに基づいて、複数のアセットバンドルに含まれるアセットを強調表示します。

Rules menu with duplicates
分析によると、Sword バンドルの間に重複したテクスチャとメッシュがあり、3 つのバンドルすべてが同じシェーダーを複製していることがわかる。

これらのアセットの重複を防ぐには、以下の 2 つの方法があります。

  1. Sword、Boss Sword、Shield のプレハブを同じバンドルに配置して、依存関係を共有する
  2. 重複したアセットを Addressables のどこかに明示的に含める

不要なアセットがメモリに残るのを防ぐために、同じバンドルに複数の持ち物のプレハブを配置することは避けたいと考えています。そのため、複製したアセットを専用のバンドル(Bundle 4 と Bundle 5)に追加します。

Addressables placed in their own bundles
重複していたテクスチャを、専用のバンドルに明示的に配置する。

バンドルの分析に加えて、分析ルールは、Fix Selected Rules を使って問題のあるアセットを自動的に修正できます。このボタンを押して、「Duplicate Asset Isolation」という名前の新しい Addressables グループを作成します。このグループには、4 つの複製されたアセットが含まれています。このグループの Bundle Mode を Pack Separately に設定して、不要になった他のアセットがメモリに保持されないようにします。

Screenshot of the editor - Addressables Groups

ステージ 5:大規模プロジェクトでアセットバンドルのメタデータサイズを削減する

このアセットバンドル戦略を使用すると、大規模な問題が発生する可能性があります。特定の時間にロードされたアセットバンドルごとに、アセットバンドルメタデータのメモリオーバーヘッドがあります。この現在の戦略を数百または数千の持ち物アイテムにスケールアップすると、このメタデータは許容できない量のメモリを消費する可能性があります。アセットバンドルメタデータの詳細については、Addressables のドキュメントをご覧ください。

Unity プロファイラーで現在のアセットバンドルメタデータによるメモリコストを表示します。メモリモジュールに移動し、メモリスナップショットを撮ります。カテゴリ Other > SerializedFile を見てみましょう。

Other - Archives
現在、1,819 個のバンドルが SerializedFile メモリを伴って読み込まれており、その合計サイズは 263MB である。

読み込まれたアセットバンドルごとに、メモリ内に SerializedFile エントリが作られます。このメモリは、バンドル内の実際のアセットではなく、アセットバンドルのメタデータです。このメタデータには次のものが含まれます。

  • 2 つのファイル読み取りバッファー
  • バンドルに含まれるすべての一意のタイプを列挙する型ツリー
  • アセットを指すコンテンツのテーブル

これら 3 つの項目のうち、ファイル読み取りバッファーが最も多くの容量を占めます。これらのバッファーは、PS4、Switch、および Windows RT ではそれぞれ 64KB であり、それ以外のプラットフォームでは 7KB です。上記の例では、1,819 バンドル* 64 KB * 2 バッファーで、バッファーのみで 227MB になります。

バッファーの数はアセットバンドルの数に比例して変化するため、メモリを削減する簡単な解決策は、実行時に読み込まれるバンドルの数を減らすことです。ただし、以前は不要なアセットがメモリに残るのを防ぐために、大きなバンドルの読み込みを回避していました。では、粒度を維持しながらバンドルの数を減らすにはどうすればよいでしょうか。

最初のステップとして手堅いものは、アプリケーションでの使われ方に基づいてアセットをグループ化することです。アプリケーションに基づいて賢明な仮定を立てることができれば、ゲーム本体のステージに基づいたグループに分けたアセットなど、常に一緒に読み込む、あるいは破棄されることがわかっているアセットをグループ化できます。

一方、アセットがいつ必要になるか、あるいは不要になるかについて、安全な仮定ができないこともあります。たとえば、オープンワールドゲームを作成している場合、プレイヤーが森で取ったアイテムを別の森に運ぶ可能性があるため、ある森にあるものすべてを 1 つのアセットバンドルに単純にグループ化することはできません。その場合、プレイヤーはアイテムを取ってきた森で使っているアセットのうち 1 つを必要としているため、森のバンドル全体がメモリに残ります。

幸い、必要なレベルの粒度を維持しながら、バンドルの数を減らす方法があります。バンドルの重複を排除する方法をもっと賢く実行すればいいのです。

私たちが実行した組み込みの重複排除分析ルールは、複数のバンドルに含まれるすべてのアセットを検出し、それらを単一の Addressables グループに効率的に移動します。そのグループを Pack Separatelly に設定すると、バンドルごとに 1 つのアセットになります。ただし、メモリの問題を発生させることなく安全にまとめることができる重複したアセットがいくつかあります。次の図について考えてみましょう。

screenshot of bundles with the packs separated into each bundle

テクスチャ「Sword_N」と「Sword_D」は同じバンドル(Bundle 1 と Bundle 2)の依存先であることがわかっています。これらのテクスチャは同じ親を持っているため、メモリの問題を引き起こすことなく安全にまとめることができます。両方の剣のテクスチャは常に読み込み、または破棄される必要があります。1 つのテクスチャを使用し、もう 1 つのテクスチャを使用しない場合はないため、テクスチャの 1 つがメモリに残る可能性があるという心配はありません。

この改善された重複排除ロジックは、独自の Addressables 分析ルールに実装できます。既存の CheckForDupeDependencies.cs ルールを元に実装することになります。完全な実装コードは、持ち物システムのサンプルで確認できます。 このシンプルなプロジェクトでは、バンドルの総数を 7 つから 5 つに減らしただけでした。しかし、アプリケーションの Addressables に数百、数千、またはそれ以上の重複アセットがあるシナリオならどうでしょうか。『Subnautica』のプロフェッショナルサービスエンゲージメントで Unknown Worlds Entertainment と協働した時、最初に組み込みの重複排除分析ルールを適用して、合計 8,718 個のバンドルがある状態になりました。その後、バンドルの親に基づいて重複排除されたアセットをグループ化するカスタムルールを適用したところ、バンドルを 5,199 個まで減らすことができました。このケースについて書いたストーリーでは、同チームとの共同作業について詳しく知ることができます。

バンドルの数を 40% 削減しつつ、コンテンツは同一の物を維持し、粒度も同じレベルを維持しました。バンドル数を 40% 削減したことで、同様に実行時の SerializedFile のサイズも 40% 削減(311MB から 184MB)されました。

結論

Addressables を使用すると、メモリ消費を大幅に削減できます。ユースケースに合わせてアセットバンドルを整理することで、メモリをさらに削減できます。結局のところ、組み込みの分析ルールは、すべてのアプリケーションにそれなりに有効に働く代わりに、保守的なものになっています。独自の分析ルールを作成することで、バンドルレイアウトを自動化し、皆さんのアプリケーションに合わせて最適化できます。メモリの問題を検出するには、頻繁にプロファイルを作成し続け、分析ウィンドウをチェックして、バンドルに明示的および暗黙的に含まれているアセットが何であるかを把握することです。 その他のベストプラクティス、使い始めるために役立つガイド、および拡張 API ドキュメントについては、Addressable アセットシステムのドキュメントを確認してください。

Addressables アセットシステムを使用してコンテンツ管理を改善する方法を学ぶための実践的なガイドが必要な場合は、専門的なトレーニングコースとして提供している「Manage Content with the Addressable Asset System」で学ぶと良いでしょう。詳細は弊社の営業担当者までお問い合わせください。現在、このトレーニングは日時を定めたライブ公開セッションとして提供されていますが、近日中にオンデマンドでも利用できるようになる予定です。

2021年3月31日 カテゴリ: テクノロジー | 12 分 で読めます

Is this article helpful for you?

Thank you for your feedback!

取り上げているトピック
関連する投稿