tanaka's Programming Memo

プログラミングについてのメモ。

AsyncOperationHandleについて

Addressable Asset SystemでシーンをLoadした時の戻り値はAsyncOperationHandle<T>です。LoadSceneAsync()で返ってくるのと違うので、以下はどうやったらよいのやら。

  • 自動的にアクティブにならないように読み込んだシーンをアクティブにする方法
  • 通信エラーを考慮した処理の組み立て方

ということで調べたことのメモです。まずは以下を読んでみました。

docs.unity3d.com

目次

意訳

AsyncOperationHandle構造体は、Addressablesの色々なAPIの戻り値で利用されます。主に、アクセス状況や結果にアクセスするのに使われます。処理結果は、AsyncOperationHandle.Release()を呼んだり、Unloadするまで残ります。

処理が完了すると、AsyncOperationHandle.StatusAsyncOperationStatus.Succeeded(成功)かAsyncOperationStatus.Failed(失敗)になります。成功していたら、AsyncOperationHandle.Resultを通じて処理の結果にアクセスできます。

処理が完了したかどうかはStatusをチェックするか、AsyncOperationHandle.Completeを使って処理が完了した時に呼び出すコールバックを登録します。

AsyncOperationHandleが返したAddressablesAPIにアクセスするためのリソースが不要になったら、Addressables.Releaseメソッドを呼び出して解放します。詳しくはこちら( Custom Operations | Package Manager UI website )。

Typed vs Typeless

多くのAddressables APIは、ジェネリックAsyncOperationHandle<T>を返します。これにより、AsyncOperationHandle.CompletedイベントのAsyncOperationHandle.Resultの型を指定して安全に扱えます。一方、非ジェネリックAsyncOperationHandleもあります。ジェネリックと非ジェネリックはコンバートして相互に利用できます。ただし、もし非ジェネリックなハンドルを、異なる型のジェネリックにキャストすると実行時に例外エラーが発生します。

読み込み完了のイベント例

AsyncOperationHandle.Completedを使って読み込み完了時に呼び出すコールバックを登録する例です。

private void TextureHandle_Completed(AsyncOperationHandle<Texture2D> handle)
{
    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        Texture2D result = handle.Result;
        // これ以降、Textureを利用できます
    }
}

void Start()
{
    AsyncOperationHandle<Texture2D> textureHandle = Addressables.LoadAsset<Texture2D>("mytexture");
    textureHandle.Completed += TextureHandle_Completed;
}

AsyncOperationHandleが返すIEnumeratorで待つ

AsyncOperationHandleIEnumeratorを返すので、コルーチンで読み込みの完了を待つことができます。

public IEnumerator Start()
{
    AsyncOperationHandle<Texture2D> handle = Addressables.Load<Texture2D>("mytexture");
    yield return handle;
    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        Texture2D texture = handle.Result;
        // 以降、Textureが使えます

        // Textureの利用が終わったら、リソースを解放します
        Addressables.Release(handle);
    }
}

Async awaitで待つ

AsyncOperationHandle.Taskプロパティーを使って、Async awaitで処理を待つこともできます。

public async Start()
{
    AsyncOperationHandle<Texture2D> handle = Addressables.Load<Texture2D>("mytexture");
    await handle.Task;
    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        Texture2D texture = handle.Result;
        // 以降、Textureが使えます

        // Textureの利用が終わったらリソースを解放します
        Addressables.Release(handle);
    }
}

意訳、以上。

シーンを有効にするには

AsyncOperationHandleの説明には載っていませんでした。スクリプトマニュアルの方にありました。

読み込みが完了した時に渡されるSceneInstanceが持つActivate()メソッドを、読み込みたくなったタイミングで呼び出せばシーンを有効にできるそうです。

おまけ。LoadSceneAsync()の定義と引数について。

public static AsyncOperationHandle<SceneInstance> LoadSceneAsync(object key, LoadSceneMode loadMode = null, bool activateOnLoad = true, int priority = 100)
Type Name 説明
System.Object key 読み込むシーンを指定するAsset Addressです。
LoadSceneMode loadMode 読み込みモードです。LoadSceneMode.Singleなら、全てのシーンを閉じてから新しいシーンを読み込みます。LoadSceneMode.Additiveなら、現在のシーンはそのままに新しいシーンを追加読み込みしてマルチシーンにします。
System.Boolean activateOnLoad falseにするとシーンの読み込みが完了しても自動的には有効になりません。バックグラウンドでシーンを読み込みたい時などに利用します。有効にしたいタイミングで、SceneInstanceのActivate()メソッドを呼び出すことで有効にできます。
System.Int32 priority シーン読み込みの非同期処理における優先順位を指定します。

第1引数がIResourceLocationでシーンを渡すメソッドもあります。

メモリ管理について

Memory Mangement | Package Manager UI website より。

LoadとUnloadをセットで正しく呼び出せば使用していたメモリーは正しく解放されます。SceneLoadingについては、Addressables.UnloadSceneAsync()メソッドに解放したいシーンを渡して解放するか、Singleモードで別のシーンを読み込むと前のシーンとハンドルが両方解放されます。

補足

Addressables.UnloadSceneAsync()の第2引数にfalseを渡すと、シーンをUnloadしても、ハンドルは解放しなくなります。シーンを再利用したい時などに利用するためのもののようです。迂闊に使わない方がよさそうです。

まとめ

以上でおおよそ把握できました。シーンをバックグラウンドで読み込んであとでアクティブにするには、読み込み時にactivateOnLoadfalseにしてシーンを読み込んで、読み込みが完了した時に返されるSceneInstanceActivate()を呼び出せばシーンを有効にできます。

エラー処理は、処理の完了を待って、戻り値のStatusで成功と失敗を見分けます。

シーンの読み込みと解放については、シーンを切り替える際にAddressables.UnloadSceneAsync()で追加シーンを解放するか、Singleモードで新しいシーンを読み込みます。

こんな感じでいけそうです。

参考URL