SceneManagerとは?

SceneManagerは、Unityでシーンの読み込みやアンロードを行うためのクラスです。

タイトル画面からゲーム本編へ移動したり、ステージごとにシーンを分けて切り替えたりと、ゲーム制作では必ず登場する仕組みですね。

UnityEngine.SceneManagement名前空間に含まれており、using UnityEngine.SceneManagement;を書けば利用できます。

そして、シーンを切り替えた時に消えてほしくないオブジェクト(BGMを鳴らすAudioSourceや、各種マネージャークラスなど)を持ち越すにはDontDestroyOnLoadを使います。

DontDestroyOnLoadは専用の記事で詳しく解説しているので、本記事ではSceneManagerを中心に扱います。

使用するバージョン

  • Unity 6 以降(Unity 2022.3 LTS でも動作確認済み)

SceneManager自体は古くから存在する機能なので、Unity 2022.3 LTS でも本記事のサンプルコードはそのまま動作します。

シーンの準備

Build Settingsへの登録

SceneManagerでロードできるシーンは、Build Profilesに登録されているシーンだけです。

FileBuild ProfilesからBuild Profilesウィンドウを開き、Scenes In Buildに対象のシーンをドラッグ&ドロップで追加します。

登録すると、左側に名前、右側にインデックス番号が表示されます。

この名前またはインデックス番号を使ってシーンをロードします。

インデックス0のシーンが、ゲーム起動時に最初に読み込まれるシーンになります。

基本的な使い方

SceneManager.LoadScene

基本的なシーン遷移はSceneManager.LoadScene()で行います。

名前指定とインデックス指定のどちらでも呼べます。

using UnityEngine;
using UnityEngine.SceneManagement;

public class TitleController : MonoBehaviour
{
    // 名前で指定してロードする
    public void LoadGameByName()
    {
        SceneManager.LoadScene("Game");
    }

    // インデックスで指定してロードする
    public void LoadGameByIndex()
    {
        // Build Settingsに登録した順番のインデックス
        SceneManager.LoadScene(1);
    }
}

呼び出した時点で現在のシーンは破棄され、新しいシーンの読み込みが始まります。

基本は名前指定がおすすめです。インデックス番号はBuild Settingsの並び替えで変わってしまうため、ミスのもとになります。

LoadSceneMode.Single と LoadSceneMode.Additive

LoadScene()の第二引数にはLoadSceneModeを指定できます。

省略時はLoadSceneMode.Singleが使われ、これは「現在のシーンを破棄してから新しいシーンを読み込む」モードです。

LoadSceneMode.Additiveを指定すると、現在のシーンを残したまま追加でシーンを読み込めます。

using UnityEngine;
using UnityEngine.SceneManagement;

public class StageLoader : MonoBehaviour
{
    public void LoadStage()
    {
        // 既存のシーンに重ねてUIシーンを読み込む
        SceneManager.LoadScene("UI", LoadSceneMode.Additive);
    }

    public void UnloadStage()
    {
        // 追加で読み込んだシーンをアンロードする
        SceneManager.UnloadSceneAsync("UI");
    }
}

Additiveは、

  • 共通UIだけを別シーンに切り出して常時表示する
  • 大きなマップを複数シーンに分割して必要な部分だけロードする

といったケースで便利です。

不要になったシーンはSceneManager.UnloadSceneAsync()で個別にアンロードできます。

なお、Additiveで読み込んだシーンは使い終わったら必ずUnloadSceneAsync()でアンロードしないとメモリが解放されません。

重ねた分だけ常駐コストが増えるので、アンロードのタイミングはセットで設計しておきましょう。

非同期ロード(LoadSceneAsync)

LoadScene()は同期的に処理されるため、シーンが大きいと一瞬画面が固まったように見えることがあります。

これを避けたい場合や、ロード画面を表示したい場合はLoadSceneAsync()を使います。

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

public class AsyncSceneLoader : MonoBehaviour
{
    public void LoadGame()
    {
        StartCoroutine(LoadSceneRoutine("Game"));
    }

    private IEnumerator LoadSceneRoutine(string sceneName)
    {
        // 非同期ロードを開始
        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);

        // 完了するまで待機
        while (!operation.isDone)
        {
            // ロード進捗を取得(0.0〜0.9)
            float progress = Mathf.Clamp01(operation.progress / 0.9f);
            Debug.Log($"Loading: {progress * 100:F0}%");
            yield return null;
        }
    }
}

ロード進捗の取得

AsyncOperation.progress0.01.0の値を返しますが、実際にはシーンの読み込みが完了した時点で0.9までしか上がりません。

残りの0.1は「ロード済みのシーンをアクティブ化する処理」に使われます。

進捗バーに表示する場合は0.9で正規化するのが定番です。

// 進捗バー用に0〜1の範囲へ正規化
float displayProgress = Mathf.Clamp01(operation.progress / 0.9f);
progressBar.value = displayProgress;

allowSceneActivation で遷移タイミングを制御する

「ロードは完了しているけれど、まだ画面を切り替えたくない」というケースもあります。

例えば、

  • ロード完了後に「タップでスタート」を表示してから遷移したい
  • アニメーションが終わってから遷移したい

といった場面ですね。

AsyncOperation.allowSceneActivationfalseにすると、ロードは進めつつ実際のシーン切り替えだけを保留できます。

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

public class TapToStartLoader : MonoBehaviour
{
    [SerializeField] private GameObject _tapToStartText;

    public void LoadGame()
    {
        StartCoroutine(LoadSceneRoutine("Game"));
    }

    private IEnumerator LoadSceneRoutine(string sceneName)
    {
        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);

        // 遷移タイミングを手動で制御する
        operation.allowSceneActivation = false;

        // progressが0.9になるまではロード中
        while (operation.progress < 0.9f)
        {
            yield return null;
        }

        // ロード完了。「タップでスタート」を表示
        _tapToStartText.SetActive(true);

        // タップされるまで待機
        while (!Input.GetMouseButtonDown(0))
        {
            yield return null;
        }

        // ここで初めてシーンを切り替える
        operation.allowSceneActivation = true;
    }
}

allowSceneActivation = falseの間はisDonetrueにならない点に注意してください。

進捗判定はprogress >= 0.9fで行うのが定番です。

アクティブシーンの切り替え(SetActiveScene)

Additiveで複数のシーンを開いている時、新しく生成したGameObjectはどのシーンに所属するのでしょうか?

答えは、現在のアクティブシーンです。

アクティブシーンはSceneManager.SetActiveScene()で切り替えられます。

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

public class StageController : MonoBehaviour
{
    private IEnumerator Start()
    {
        // ステージシーンを追加ロード
        yield return SceneManager.LoadSceneAsync("Stage1", LoadSceneMode.Additive);

        // ロードしたシーンを取得してアクティブに切り替える
        Scene stageScene = SceneManager.GetSceneByName("Stage1");
        SceneManager.SetActiveScene(stageScene);
    }
}

アクティブシーンを切り替えると、

  • Instantiate()で生成したオブジェクトの所属シーン
  • ライティング設定(環境光やスカイボックス)

がそのシーンのものに切り替わります。

Additiveを使う時はセットで覚えておきましょう。

まとめ

今回はSceneManagerによるシーンの読み込みと切り替えについて解説しました。

ポイントを振り返ると、

  • SceneManager.LoadScene()でシーンを切り替えられる
  • LoadSceneMode.Additiveで複数のシーンを重ねて読み込める
  • LoadSceneAsync()を使えば非同期ロードや進捗表示、allowSceneActivationでの遷移タイミング制御も可能
  • SetActiveScene()で生成先のシーンやライティングを切り替えられる
  • シーンを跨いでデータを残すにはDontDestroyOnLoadScriptableObjectstaticを使い分ける

といったところです。

シーン構成の設計は、プロジェクトが大きくなるほど効いてきます。早めに押さえておくと、後の拡張がぐっと楽になりますよ。