【Unity】Addressableアセットシステムで同期的にリソースをロードする(追記あり)

UnityのAddressableアセットシステムで同期的にリソースをロードする方法をまとめました。

Unity2020.1.10
Addressables 1.15.1

【追記】同期ロードが公式にサポートされました

本記事はまだAddressableで同期的なロードが実装されていない時に書いた記事です。

その後Addressbles 1.17.4にて同期ロードが公式にサポートされました。
これについては以下の記事にまとめていますのでこちらをご参照ください。

light11.hatenadiary.com

同期的にロード?

Addressableアセットシステムではリソースのロードに非同期のAPIを使用します。

var handle = Addressables.LoadAssetAsync<GameObject>("FooPrefab");
await handle.Task;
var prefab = handle.Result;

Addressables.Release(handle);

いま、このリソースを同期的に読み込むことを考えます。
以下のようにTaskがWait()できればいいのですがAddressableは対応してません(デッドロックします)。

// ※これやるとUnityが固まります※
var handle = Addressables.LoadAssetAsync<GameObject>("FooPrefab");
handle.Task.Wait();
var prefab = handle.Result;

Addressables.Release(handle);

本記事ではAddressableアセットシステムでリソースを同期的に読み込む方法についてまとめます。

1. プリロードする

まずスタンダードな解決策としてリソースをプリロードしておくことが考えられます。
Addressablesは以下のように、一度読み込んだリソースはawaitしなくても同期的に取得できる仕様になっています。

// FooPrefabをプリロードしておく
var handle1 = Addressables.LoadAssetAsync<GameObject>("FooPrefab");
await handle1.Task;
Debug.Log(handle1.Result); 

// プリロードしたものはawaitしなくても使える
var handle2 = Addressables.LoadAssetAsync<GameObject>("FooPrefab");
Debug.Log(handle2.Result);

// Instantiateも同様
var handle3 = Addressables.InstantiateAsync("FooPrefab");
Debug.Log(handle3.Result);


Addressables.Release(handle1);
Addressables.Release(handle2);
Addressables.Release(handle3);

そのため例えばロード画面で必要なリソースをプリロードしておけば、必要な時に同期的にリソースを取得することができます。

2. 専用のProviderを使う

二つ目の方法として、AssetBundleのロードやAssetBundleからAssetをロードするためのProviderを差し替える方法があります。
これについては以下のリポジトリにUnityの公式がサンプルを上げているので、これを参考にすることができます。

github.com

SyncAddressablesフォルダ内のスクリプトを一式取り込んだ上で、以下の手順によりリソースを同期ロードできることが確認できます。

  1. 任意のAddressablesグループに同期ロードしたいアセットをまとめる
  2. グループのInspectorからAssetProviderとAssetBundleProviderを同期用のものに差し替える(下図)
  3. ビルドする
  4. Play Mode ScriptをUse Existing Buildにする
  5. SyncAddressables.LoadAsset() で読み込む

f:id:halya_11:20210120213119p:plain
Providerを差し替える

これでリソースを同期的にロードできます。

が、このサンプルをそのまま使う場合にはいくつか注意点・問題点があるのでそれを以下にまとめます。

公式サンプルの注意点① 同期Providerを使うグループは非同期読みできない

一つ目はProviderがAddressablesグループごとにしか設定できない点です。
つまりSync系のProviderを設定したグループのリソースは必ず同期で読まれることになります。

Addressables.LoadAssetAsync()を使っても同期的に読まれるので重いリソースを読み込んだら固まります。

公式サンプルの注意点② ダウンロードはできない

Addressablesには本来、「リソースロード時にストレージに当該リソースが無かったらダウンロードする」という機能があります。
Addressables.LoadAssetAsync()を呼ぶだけで内部的にはUnityWebRequestを使ってリソースをダウンロードしてくれます。

そもそもこれがAddressablesのAPIが非同期のみである大きな理由ともいえるかと思いますが、
同期用Providerを使うことによってダウンロードする機能は使えなくなります(ストレージにリソースがあることがロードの前提となります)。

公式サンプルの注意点(問題点)③ Android非対応

もう一つ大きな注意点というか問題点として、公式のサンプルはAndroidに対応していないという点があります。
これはAndroidにおいてアプリに組み込まれているリソースを読み込む方法が若干複雑なことに由来するもので、
技術的に解決できる問題ではありますが、そのためには公式のAssetBundleロード用のProviderをAndroidに対応する形に書き換える必要があります。

3. Unityの対応を待つ

さて3つ目の解決策(?)としてはUnity公式の対応を待つということがあります。
Addressablesの同期APIについては、そもそも必要なのかどうかという点から長らく議論されています。

forum.unity.com

公式としてはあまり意欲を見せていなかったように思っていたのですが、
最新の投稿(2021/1/13)ではそのうちプレビューバージョンに同期読みを実装すると書いています。
@su10_dev さんがおしえてくれましたm(_ _ )m

We're actually working on it right now. It'll be released in a preview version of the package first. But we're looking to have it sometime this month. Not the next release (in a couple days), but likely the release after that or so. Can't make any concrete deadline promises. Just know it's coming.

そんなわけでUnityの公式対応を待つ、というのも選択肢の一つとして挙がることになりました。

実装されたらこの記事に追加しようと思います(忘れそう)。
新しい記事として投稿しました。

light11.hatenadiary.com

参考

github.com

forum.unity.com