LoginSignup
2
3

More than 1 year has passed since last update.

Addressablesでのランタイム更新にはまった話

Last updated at Posted at 2022-12-03

前置き

「Addressablesを使えばアプリ更新無しでリソース更新し放題!」
気が付けば、軽い気持ちで手を出してしまった。よくわからないが、恐らく先人の歩んできた道に沿って行動すればなんてことはない。多少苦戦するかもしれないが、せいぜい獣道くらいだろう。鈴はもった。熊撃退スプレーもある。万全だ。

翌日、暗い部屋で一人頭を抱えながら自身の将来、日本の行く末、世界の終焉について物思いにふけるおじさんと化していた。都会の空気を長く吸いすぎたのだろう。獣道をなめていた。

要件

開発中アプリでは、運営中に可能な限り低コストでアセット更新を行いたいため、「アプリを再起動しなくてもアセット更新があれば取得・反映」出来るようにする必要がある。
また事前に必要なリソースはダウンロードしておき、プレイ中のもたつきを極力抑えたい。

結論

結論からのべると、いつもの感覚で async/await を行うとはまる。
Addressablesでなんらかの処理をする場合、必ず Addressables.Release にて解放する必要がある。これはDownloadDependenciesAsyncやLoadResourceLocationsAsyncなどの操作についても同様であり、それを忘れると参照が外れない(ここにはまっていた)。

当たり前だが、勝手によしなにGC的な何かが走ってうまいことしてくれるとか、そんな優しい世界ではなかった。よくよく調べると至るところに書いてある。ちゃんと調べよう(自戒)

実際の実装

今回の要件を満たすため、下記フローでそれを実現しようとした。
※Addresablesのバージョンは、対応時最新の 1.19.19 を使用。

  1. 起動時に更新があるかチェック
  2. 更新があれば最新アセットをダウンロードし、反映
  3. インゲームへ遷移
  4. タイトルへ戻る場合、再度1を実施

カタログの更新チェック

ロード画面で、カタログの更新チェックを行う。

public async UniTask<bool> UpdateCatalog()
{
    var modifyList = await Addressables.CheckForCatalogUpdates();
    if (modifyList.Count > 0)
    {
        await Addressables.UpdateCatalogs(modifyList);
    }
    return modifyList.Count > 0;
}

事前に更新アセットをダウンロード

今回、事前ダウンロードしたものは Release せず保持しておきたかったので、必要なタイミングで解放。
※進捗取得、LoadResourceLocationsAsyncを使用したグルーピングについては割愛。

public async UniTask Preload(string label) {
    // 前回キャッシュしたAsyncOperationHandleを開放
    if (downloadHandleCache.HasValue) {
        Addressables.Release(downloadHandleCache.Value);
    }

    // 最新のアセットを取得
    var downloadHandle = Addressables.DownloadDependenciesAsync(label);
    await downloadHandle.Task;
    downloadHandleCache = downloadHandle;
}

インスタンス作成

必ず Release するを念頭において、Addressables EventViewerなどを利用しながら参照が残らないことを確認しつつ実装を進めます。
この時、生成したインスタンスの破棄に応じて、自動的に Release する仕組みを実装しておくとはかどります。詳細は省きますが、OnDestroy時に処理を挟み込めば良いです。

参考リンク: Addressables EventViewer | 公式マニュアル

public T InstantiatePrefab<T>(string path) {
    var handle = Addressables.InstantiateAsync($"{path}.prefab");
    // 同期的に読み込み
    var go = handle.WaitForCompletion();
    // 拡張メソッド/GameObjectの状態を監視し、破棄されれば内部的にReleaseする
    handle.Watch(go);
    return go.GetComponent<T>();
}

最後に

アセット周りは何かと厄介なので後回しにしがちですが、早い段階で検証と対応を進めておくと後々スムーズに進行出来ると思います。私はもうやりたくないです..が、カタログ運用でもはまると思うので、その時はまた記事にします...。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3