Resources フォルダの限界
Unity で古くからアセットを動的に読み込む手段として広く使われてきたのが Resources フォルダです。Resources.Load を呼ぶだけでアセットを取得できる手軽さから、気軽に採用しているプロジェクトも多いのではないでしょうか。
しかし、大規模プロジェクトでは Resources フォルダを使い続けることにいくつかの問題が生じます。Resources フォルダ内のアセットはすべてビルドに含まれるため、使っていないアセットまでアプリサイズを増やしてしまいます。さらに、Resources のアセットは起動時にインデックスがメモリへ展開されるため、アプリの起動時間とメモリ使用量が膨らむ原因にもなります。依存関係の追跡も難しく、不要アセットを安全に削除するには全文検索が必要になりがちです。
Addressable Asset System とは
Addressable Asset System ( 以下 Addressables ) は、Resources フォルダの代替として Unity が公式に提供しているアセット管理の仕組みです。アセットに任意のアドレス文字列を割り当て、実行時にそのアドレスを指定してロードするアドレス指向の参照モデルを採用しています。
アセットは Addressables 用のビルドパイプラインでバンドル化され、アプリ本体と切り離して配信することもできます。これにより、ビルドサイズを小さく保ちつつ、必要なタイミングで必要な分だけアセットを読み込む設計が実現できます。リモート配信にも対応しているため、アプリリリース後の差分アップデートやストア容量を抑えたダウンロードコンテンツの配信とも相性が良い仕組みです。
Addressables の導入手順
Addressables は Package Manager からインストールします。Window > Package Manager を開き、Unity Registry から Addressables を選択してインストールしましょう。
インストール後、Window > Asset Management > Addressables > Groups を開くと管理ウィンドウが表示されます。最初に Create Addressables Settings ボタンを押して設定ファイルを作成し、その後 Project ウィンドウでアセットを選択して Inspector 上部の Addressable チェックボックスをオンにすると、そのアセットが Addressables の管理対象になります。
アドレス文字列はデフォルトでアセットパスが自動設定されますが、Addressables Groups ウィンドウから任意の名前に変更できます。Enemy/Goblin のようにジャンル別に整理しておくと、コードから参照する際に扱いやすくなります。
スクリプトからのロード方法
アセットをロードするには Addressables.LoadAssetAsync を使用します。以下は Prefab をロードしてインスタンス化する基本形です。
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class EnemySpawner : MonoBehaviour
{
private AsyncOperationHandle<GameObject> handle;
private async void Start()
{
handle = Addressables.LoadAssetAsync<GameObject>("Enemy/Goblin");
GameObject prefab = await handle.Task;
Instantiate(prefab, transform.position, Quaternion.identity);
}
private void OnDestroy()
{
if (handle.IsValid())
{
Addressables.Release(handle);
}
}
}
LoadAssetAsync は AsyncOperationHandle を返すため、await で完了を待つか、Completed コールバックに処理を登録する形で取得結果を受け取ります。ロードしたアセットはメモリを占有し続けるため、不要になったタイミングで必ず Addressables.Release を呼び出して解放してください。
Prefab をロードしてそのままインスタンス化したい場合は、Addressables.InstantiateAsync を使うとロードとインスタンス化が同時に行え、個別のインスタンスに対して ReleaseInstance で解放できます。
ラベルでまとめてロード
Addressables ではアセット 1 つずつにアドレスを付ける他に、ラベルを付与してグループ単位でロードすることも可能です。たとえばステージ 1 に登場するアセットすべてに stage1 ラベルを付けておけば、1 回の呼び出しでまとめて読み込めます。
var handle = Addressables.LoadAssetsAsync<Texture2D>("stage1", null);
IList<Texture2D> textures = await handle.Task;
ロード完了後はすべてのテクスチャがリストで取得できるため、シーン単位のバンドルを効率的に扱えます。同様にステージ終了時に一括で解放すれば、シーン遷移に合わせたメモリ管理が実現できます。
AssetReference で型安全に参照する
文字列アドレスで指定する方法は柔軟ですが、タイプミスやリネームによる破損に気付きにくいという欠点があります。そこで活用したいのが AssetReference です。MonoBehaviour のフィールドとして AssetReferenceGameObject を宣言すると、Inspector からドラッグアンドドロップでアセットをアサインでき、リネームにも自動で追従します。
public class WeaponLoader : MonoBehaviour
{
[SerializeField] private AssetReferenceGameObject weaponReference;
private async void Start()
{
GameObject weapon = await weaponReference.LoadAssetAsync<GameObject>().Task;
Instantiate(weapon, transform);
}
}
型パラメータ付きの AssetReferenceT を使うことで、意図しない型のアセットがアサインされるのを防げます。文字列ベースでは避けられないエラーをコンパイル時と Inspector の両面で抑えられるため、実装上のミスを減らしたい場面で積極的に採用したい書き方です。
メモリリークを防ぐための解放ルール
Addressables を扱ううえでもっとも注意すべきは、ロードしたアセットの解放です。LoadAssetAsync と Release は 1 対 1 で対応しており、Release を呼び忘れるとアセットはメモリ上に残り続けます。実装上のルールとして、次の点を徹底すると事故を防ぎやすくなります。
- LoadAssetAsync で取得した AsyncOperationHandle は必ずフィールドに保持する
- 不要になった時点で Addressables.Release を呼び出す
- シーンやオブジェクトの破棄タイミングで解放する場合は OnDestroy に処理を書く
特に InstantiateAsync は内部で暗黙的に参照カウントを増やすため、Destroy ではなく ReleaseInstance を使ってインスタンスを破棄する必要があります。通常の Destroy を呼んでしまうとインスタンスは消えても参照カウントが残り、メモリリークにつながるので注意してください。
リモート配信への拡張
Addressables の真価はリモート配信に踏み込むと発揮されます。Addressables Groups で各グループの Build Path と Load Path をリモート用のパスに切り替え、CDN や自社サーバーにバンドルをアップロードすれば、アプリ本体のインストールサイズを抑えつつ追加コンテンツを後から配信できるようになります。
アプリ起動時に Addressables.CheckForCatalogUpdates を呼び出してカタログの更新を確認し、差分があれば UpdateCatalogs でダウンロードする流れが基本です。大規模タイトルやライブ運用を前提としたゲームでは、この仕組みがそのまま運用基盤になります。
まとめ
Addressable Asset System は、Resources フォルダの制約を解消するだけでなく、大規模プロジェクトで必要になる柔軟なアセット管理基盤を一式提供してくれる仕組みです。アドレス指向のロード、ラベルによるグループ操作、型安全な AssetReference、そしてリモート配信への拡張と、運用規模が大きくなるほど恩恵が増します。
新規プロジェクトでは最初から Addressables を前提に設計し、既存プロジェクトでも段階的に Resources から移行しておくことで、後々のメモリ問題や配信課題を大きく軽減できます。
コメント