Unityでシーン移動して、立て続けに移動先のシーンで何かしたい時があるとします。例えば背景を動的に変えたい時とかです。
この時、同期処理であるLoadSceneメソッドを使って下記のように書くと、シーン移動が終わらない内に移動先シーンのゲームオブジェクトを取得しようとしてNullReferenceExceptionが発生します。
// シーン移動
LoadScene("NextScene");
// 移動先シーンのゲームオブジェクトを取得して何か処理する
このような時にUnityはシーン移動用の非同期メソッド「LoadSceneAsync」を提供しています。すなわち、このメソッドを使って次のようにすればシーン移動の完了を待って何か処理をさせることができます。
// 非同期でシーン移動
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync("NextScene");
// シーン移動が完了するまで待機
while (!asyncLoad.isDone)
{
yield return null;
}
// 移動先シーンのゲームオブジェクトを取得して何か処理する
次に、非同期のシーン移動メソッドとして使いまわせるようにしておきたいと思います。ここではコルーチンを使った書き方を示します。
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Foo : MonoBehaviour
{
public static Foo foo;
// シーン移動でオブジェクトが破棄されないようにしておく
void Awake()
{
if (foo == null)
{
foo = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
// 非同期シーン移動の呼び出し元メソッド
public void StartLoadAsync(string nextScene)
{
StartCoroutine(LoadAsync(nextScene));
}
// 非同期シーン移動のコルーチン
IEnumerator LoadAsync(string nextScene)
{
// 非同期でシーン移動
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(nextScene);
// シーン移動が完了するまで待機
while (!asyncLoad.isDone)
{
yield return null;
}
// 移動先シーンのゲームオブジェクトを取得して何か処理する }
}
}
このクラスを適当なゲームオブジェクトにアタッチしたら、あとはシーン移動したい箇所で下記のようにしてStartLoadAsyncメソッドを呼び出すだけです。シーン移動を待ってから何か動的に処理を施したい場合は、別途引数で渡すようにしてもよいと思います。
Foo.foo.StartLoadAsync("NextScene");
上のコードで、なぜシーン移動でオブジェクトが破棄されないようにしているのか?、StartCoroutineは呼び出し元のシーンで直接呼べばいいのではないか?、と思われた方もいらっしゃるかもしれません。
しかし、シーンをまたがった処理になるので、それだとうまくいきません。なぜなら、シーン移動すると移動元シーンのオブジェクトは破棄されてしまうからです。
そのためシーン移動しても破棄されないクラスの中にStartCoroutineも含めて定義しておく必要があるのです。