アセットプロバイダー
Addressables 上の非同期アセットロード。キーごとの参照カウントとスコープ単位の一括クリーンアップ付き。
1 行で書ける非同期アセットロード。共有は自動、クリーンアップはリークを許しません。
| インターフェース | IAssetProvider |
| オフスイッチ | NullAssetProvider |
| アセンブリ | CommonGameSystem.Assets(オプション — Addressables パッケージが必要) |
| 起動 | 起動時に自動登録 — セットアップ不要 |
概要
アセットプロバイダーは、Unity の Addressables システムをシンプルな参照カウント付きインターフェースの背後にラップします。アドレス文字列でアセットをロードすると、同じキーを使うすべての呼び出し元が同一のロード済みアセットを共有します — 参照カウントが保持者の数を追跡し、Release(key) は最後の呼び出し元が解放したときにだけアンロードします。これにより「このテクスチャは誰がアンロードするのか?」という調整問題は、フレームワークが肩代わりする帳簿付けに変わります。
ロードをスコープにまとめることもできます:CreateScope() で作成し、それを通してロードすれば、スコープを破棄するだけでロードしたすべてが 1 回の呼び出しで解放されます。スコープは「この画面が必要としたすべて」の自然な単位です。
メモリバジェットも自動追い出しもありません — ロードし、参照を数え、スコープでクリーンアップするだけです。保持しているものが、そのままメモリに残るものです。
このサービスはオプションの CommonGameSystem.Assets アセンブリにあります。プロジェクトから Addressables パッケージを削除すると、このアセンブリは自動的に除外され、フレームワークの残りはそのままコンパイルされ続けます。
クイックスタート
Awake でサービスを解決してキャッシュします:
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)] // Run after the framework has started.
public class MyGameManager : MonoBehaviour
{
private IAssetProvider _assets;
private void Awake()
{
_assets = ServiceLocator.Resolve<IAssetProvider>();
}
private async void OnEnable()
{
var prefab = await _assets.LoadAsync<GameObject>("assets/my-prefab");
if (prefab != null)
Instantiate(prefab);
}
}
短いエイリアスも使えます:IAssetProvider assets = SL.Resolve<IAssetProvider>();
API リファレンス
Load / Release(参照カウント付き)
Task<T> LoadAsync<T>(string key, CancellationToken ct = default)— 指定アドレスのアセットを型T(GameObject、ScriptableObject、Sprite、TextAsset、AudioClipなど)としてロードします。同じキーを再度ロードすると同じアセットが返り、参照カウントが増えます。返されるTask<T>が null になることはありません。ランタイム障害 — キーが見つからない、ロードの失敗 — はdefault(T)を返して警告をログします。決して例外は投げません。void Release(string key)— キーの参照カウントを 1 減らします。カウントが 0 に達するとアセットがアンロードされます。すでに 0 の場合は警告をログして何もしません。例外は投げません。
Preload / Query
Task PreloadAsync<T>(string key, CancellationToken ct = default)— アセットを返さずにロードしてキャッシュに保持します。LoadAsync<T>と同じ参照カウントを使うので、後のRelease(key)と対にしてください。現在のシーンをプレイ中に次のシーンのアセットをウォームアップするのに便利です。bool IsLoaded(string key)— キーの参照カウントが 0 より大きければtrueを返します。例外もログも発生しません。int LoadedCount { get; }— 現在保持している異なるキーの数。これはリークのシグナルです:数えるのはキーであって、全参照カウントの合計ではありません。
スコープ
IAssetScope CreateScope()— スコープを作成します。スコープを通してロードすれば、Dispose()がスコープのロードしたすべてを解放します — 記録されたロード 1 件につきRelease(key)1 回です。IAssetScope— 同じLoadAsync<T>、PreloadAsync<T>、IsLoaded、LoadedCountメンバーに加え、一括クリーンアップ用のDispose()を提供します。
使用例
シーンのプレハブをロードし、作業が終わったらまとめてクリーンアップします:
using CommonGameSystem.Core;
using UnityEngine;
[DefaultExecutionOrder(100)]
public class ScenePopulator : MonoBehaviour
{
private IAssetProvider _assets;
private async void Start()
{
_assets = ServiceLocator.Resolve<IAssetProvider>();
using var scope = _assets.CreateScope();
var player = await scope.LoadAsync<GameObject>("assets/player");
var ui = await scope.LoadAsync<GameObject>("assets/ui-root");
if (player != null) Instantiate(player, transform);
if (ui != null) Instantiate(ui);
// When the scope is disposed, it releases both
// "assets/player" and "assets/ui-root".
}
}
共有キーに対する参照カウントの振る舞い:
var go1 = await _assets.LoadAsync<GameObject>("assets/coin"); // reference count 1
var go2 = await _assets.LoadAsync<GameObject>("assets/coin"); // reference count 2, SAME asset
_assets.Release("assets/coin"); // reference count 1
_assets.Release("assets/coin"); // reference count 0 — the asset unloads
無効化する
ServiceLocator.Replace<IAssetProvider>(new NullAssetProvider());
これですべてのロードがミュートされます。LoadAsync<T>() は default(T) を保持する完了済みタスクを返し、IsLoaded は常に false、LoadedCount は常に 0 です。Addressables には一切触れません。テストや、アセット機能を丸ごと無効にしたい場合に便利です。構築時に 1 回だけ警告をログするので(呼び出しごとではありません)、有効になっていることが分かります。
よくある落とし穴
- 参照カウントが唯一のアンロードトリガーです。 メモリバジェットも LRU 追い出しもアンロードタイマーもありません。ロードして解放しなければ、アセットはメモリに残り続けます。リークの発見には
LoadedCountを監視してください。 LoadAsync<T>はコンパイル時ジェネリックメソッドです。 IL2CPP ビルドでは、ロードするアセット型(GameObject、ScriptableObject、Sprite、TextAsset、AudioClip)がプロジェクトのlink.xmlで保存されている必要があります。正確なエントリはパッケージ README の IL2CPP セクションにあります。- スコープは実行中のロードも捕捉します。 ロードの実行中にスコープが破棄された場合、そのロードは通常どおり完了した後、即座に解放されます。孤児になるものはありません。
- 型の不一致は安全です。 あるキーを最初に
LoadAsync<GameObject>("key")としてロードし、後からLoadAsync<Sprite>("key")を呼ぶと、2 回目の呼び出しは null を返して警告をログします — 例外にはなりません。参照カウントは型ではなくキー名に属します。 - null や空のキーはコード側のバグとして扱われます。
LoadAsync(null)はArgumentNullExceptionを、空白のみのキーはArgumentExceptionを投げます。これらはプログラミングエラーであり、決して例外を投げない上記のランタイムデータ障害とは異なります。 - メインスレッド専用です。
Release、IsLoaded、CreateScope、スコープのDisposeを含むすべての公開メンバーは、メインスレッドで実行しなければなりません。ワーカースレッドからは、スケジューラーのPost()を使って呼び出しをメインスレッドに戻してください。
関連ページ
- サービスロケーター — サービスの解決
- スケジューラー — タイマーとメインスレッドでのコード実行
- アドレッサブルシーン — Addressables からの加算シーンのロード