【Unity】【Addressables】シーンのロードと初期化タイミングをちゃんと理解する

Unityにおけるシーンのロードと初期化タイミングについてまとめました。

シーンのロード

UnityではSceneManagerやAddressablesのAPIを使うことでシーンをロードでします。
シーンのロードは、シーン自体の読み込みと、そのシーンに存在しているアセットを読み込んで初期化するプロセスに分解できます。

シーン自体が読み込まれた状態ではまたシーンに存在する各コンポーネントのAwakeなどは呼ばれていない状態です。
また、この時点でSceneManager.SetActiveSceneでこのシーンをアクティブにしようとするとエラーが発生します。
マルチシーン編集時にUnload Sceneをしてnot loadedになった状態、ともいうことができます。

f:id:halya_11:20210616161951g:plain
not loaded

この状態から、シーン上のアセットなどの読み込み、初期化が完了するとそれらにアクセスできるようになります。
これはマルチシーン編集時にLoad Sceneをした状態であるといえます。

f:id:halya_11:20210616162219g:plain
Load Scene

本記事ではこの挙動について、SceneManagerとAddressablesでちゃんと制御する方法をまとめます。

SceneManager.LoadScene

まずSceneManager.LoadSceneで同期的にシーンを読み込むケースです。
このAPIはシーン自体を同期的に読み込んで返却します。

ただしこの時点ではシーンのアセットはロードされていない、not loadedな状態なので、以下のようにScene.isLoadedがtrueになるのを待つ必要があります。

using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Example : MonoBehaviour
{
    private IEnumerator Start()
    {
        // シーン自体を同期的にロードする
        var scene = SceneManager.LoadScene("Test", new LoadSceneParameters(LoadSceneMode.Additive));

        // この時点ではシーン内のコンポーネントのAwakeやStartが呼ばれていない状態
        // SetActiveSceneをしようとするとエラー

        // シーン上のアセットのロードを待つ
        yield return new WaitUntil(() => scene.isLoaded);

        // この時点ではシーン内のコンポーネントのAwakeやStartが呼ばれた状態
        // SetActiveSceneもできる
        SceneManager.SetActiveScene(scene);
    }
}

SceneManager.LoadSceneAsync

SceneManager.LoadSceneAsyncを使う場合には挙動はもう少しシンプルです。
以下のように、AsyncOpretaionHandleが完了を示す頃にはシーン上のアセットの初期化まで完了した状態になります。

using System.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Example : MonoBehaviour
{
    private IEnumerator Start()
    {
        // シーン自体及びシーン上のアセットをロード、初期化
        yield return SceneManager.LoadSceneAsync("Test", new LoadSceneParameters(LoadSceneMode.Additive));

        // この時点でシーン内のコンポーネントのAwakeやStartが呼ばれた状態
        // SetActiveSceneもできる
        var scene = SceneManager.GetSceneByName("Test");
        SceneManager.SetActiveScene(scene);
    }
}
2022/02/25追記

以下のようにLoadSceneAsync()の戻り値であるAsyncOperationallowSceneActivationをfalseにすることでnot loaded状態にできることを教えていただきました。ありがとうございます!

var op = SceneManager.LoadSceneAsync("Test", new LoadSceneParameters(LoadSceneMode.Additive));
op.allowSceneActivation = false;
yield return op;

Addressables.LoadSceneAsync

Addressablesではこの挙動をもう少し細かく制御できます。
まず以下のようにロード時の引数activateOnLoadをtrueに設定すると、シーン上のアセットの初期化まで行われる挙動になります。

using System.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.SceneManagement;

public class Example : MonoBehaviour
{
    private IEnumerator Start()
    {
        // activateOnLoadをtrue(デフォルト値)にするとシーン上のアセットの初期化まで行われる
        var op = Addressables.LoadSceneAsync("Test", LoadSceneMode.Additive, true);
        yield return op;
        
        // この時点でシーン内のコンポーネントのAwakeやStartが呼ばれた状態
        // SetActiveSceneもできる
        var scene = op.Result.Scene;
        SceneManager.SetActiveScene(scene);
    }
}

これをfalseにするとシーン自体のロードだけが行われます。
アセットの初期化をするには以下のようにSceneInstanceのActivateAsyncを呼ぶ必要があります。

using System.Collections;
using UnityEditor;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.SceneManagement;

public class Example : MonoBehaviour
{
    private IEnumerator Start()
    {
        // activateOnLoadをfalseにするとシーン自体のロードだけ行われる
        var op = Addressables.LoadSceneAsync("Test", LoadSceneMode.Additive, false);
        yield return op;
        
        // この時点ではシーン内のコンポーネントのAwakeやStartが呼ばれていない状態
        // SetActiveSceneをしようとするとエラー
        
        // シーン上のアセットをロード
        yield return op.Result.ActivateAsync();
        
        // この時点でシーン内のコンポーネントのAwakeやStartが呼ばれた状態
        // SetActiveSceneもできる
        var scene = op.Result.Scene;
        SceneManager.SetActiveScene(scene);
    }
}