Unityで画面やステージを切り替えるには、SceneManagerで次のシーンを読み込みます。まず対象シーンをビルド設定に登録し、ボタンからLoadSceneを呼ぶところまで作ってみましょう。

使い分けは3つ。
シンプルに切り替える → LoadScene
ロード画面を見せる → LoadSceneAsync
今のシーンを残して重ねる → LoadSceneMode.Additive

Unity 6.0の公式資料をもとに、シーン登録・ボタン接続・進捗表示・データ保持の順に紹介します。

シーンとは?画面やステージをまとめる単位

シーンは、カメラ・UI・キャラクターなどのGameObjectをまとめて保存する単位です。タイトル、ステージ、結果画面を別々のシーンにすると、場面ごとに編集できます。

データ選択からステージ選択、ゲーム、リザルトへ進むシーン遷移
シーンの分け方の例。結果画面からステージ選択へ戻る流れも作れます。

シーンを作り、Build Profilesに登録する

  1. ProjectウィンドウのAssets内にScenesフォルダーを用意し、右クリックのCreateからSceneを作成します。メニューが階層化されている場合はScene → Sceneを選びます。
  2. SampleSceneとNew Sceneの2つを用意します。名前は自由ですが、この後のコードと一致させてください。
  3. Unity 6ではFile → Build Profiles → Platforms → Scene Listを開き、2つのシーンをドラッグします。開いているシーンはAdd Open Scenesでも追加できます。
  4. 両方のチェックをオンにし、最初に起動するシーンを先頭(インデックス0)に置きます。
ScenesフォルダーにNew SceneとSampleSceneを保存
切り替え元と切り替え先の2つのシーンを用意します。
Build ProfilesのScene Listに2つのシーンを登録
チェックが付いたシーンがビルド対象です。右端の0・1がビルドインデックスです。

Unity 2022.3などではFile → Build Settings → Scenes In Buildを使います。Unity 6でOverride Global Scene Listを有効にしたプロファイルを使う場合は、そのプロファイル側のシーン一覧も確認してください。

登録方法の詳細はUnity公式のScene Listの説明を参照できます。

ボタンでシーンを切り替える:LoadScene

まずは小さな例です。SceneChanger.csを作り、次のコードを保存します。クラス名とファイル名はそろえてください。

using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneChanger : MonoBehaviour
{
    public void ChangeScene()
    {
        SceneManager.LoadScene("New Scene");
    }
}
  1. SampleSceneのHierarchyでCreate Emptyを選び、SceneControllerという空のGameObjectを作ります。SceneChanger.csをアタッチします。
  2. HierarchyのUI → Button – TextMeshProでボタンを作ります。CanvasとEventSystemがあることも確認します。
  3. ButtonコンポーネントのOn Click()で「+」を押し、SceneControllerをオブジェクト欄へドラッグします。
  4. 関数の一覧からSceneChanger → ChangeScene()を選びます。再生中にボタンを押し、HierarchyがNew Sceneへ切り替われば接続できています。
シーン切り替え用のボタンとスクリプトを置いたUnityエディター
ボタンと制御用GameObjectを用意した画面例。接続する関数は本文の手順を確認してください。
ボタンからNew Sceneへ切り替える操作例。Hierarchyのシーン名に注目してください。

第2引数を省略するとSingleになり、現在のシーン群を置き換えます。残しておきたいGameObjectは、この後で説明するDontDestroyOnLoadなどで管理します。

LoadSceneには、.unityを付けずにシーン名を渡します。同名のシーンが複数ある場合は、Assets/Scenes/New Sceneのようにパスで指定しましょう。数値のビルドインデックスでも指定できますが、一覧の並べ替えで番号が変わる点に気を付けてください。

ロード画面を表示する:LoadSceneAsync

読み込み中に進捗を表示したい場合はLoadSceneAsyncを使います。非同期でも切り替え時の処理負荷は残るので、重いシーンでは実機で動きを確認しましょう。

ロード画面とスクリプトを接続する

  1. Canvasの子にロード画面用のPanelを作り、その子にSliderとText – TextMeshProを置きます。Panelは最初は非表示にします。
  2. SliderはMin Valueを0、Max Valueを1、Whole Numbersをオフにします。操作用ではないのでInteractableもオフにします。
  3. SceneLoader.csを作り、常に有効なSceneControllerへアタッチします。非表示にするPanel自身や、その子には付けないでください。
  4. InspectorでScene Nameに切り替え先を指定し、Loading Screen・Progress Bar・Progress Textへそれぞれ割り当てます。
  5. ボタンのOn Click()をSceneLoader → LoadScene()に変更します。同期のChangeScene()の登録は外し、どちらか一方だけを呼びます。
using System.Collections;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class SceneLoader : MonoBehaviour
{
    [SerializeField] private string sceneName = "New Scene";
    [SerializeField] private GameObject loadingScreen;
    [SerializeField] private Slider progressBar;
    [SerializeField] private TMP_Text progressText;
    private bool isLoading;

    public void LoadScene()
    {
        if (isLoading) return;
        if (loadingScreen == null || progressBar == null ||
            progressText == null)
        {
            Debug.LogError("ロード画面・Slider・テキストを割り当ててください。");
            return;
        }
        isLoading = true;
        StartCoroutine(LoadRoutine());
    }

    private IEnumerator LoadRoutine()
    {
        loadingScreen.SetActive(true);
        progressBar.value = 0f;
        progressText.text = "0%";
        yield return null;

        AsyncOperation operation = null;
        try
        {
            operation = SceneManager.LoadSceneAsync(sceneName);
        }
        catch (System.Exception exception)
        {
            Debug.LogException(exception);
        }
        if (operation == null)
        {
            loadingScreen.SetActive(false);
            isLoading = false;
            yield break;
        }

        operation.allowSceneActivation = false;
        while (operation.progress < 0.9f)
        {
            float progress = operation.progress / 0.9f;
            progressBar.value = progress;
            progressText.text = (progress * 100f).ToString("F0") + "%";
            yield return null;
        }

        progressBar.value = 1f;
        progressText.text = "読み込み完了・切り替え中";
        yield return null;
        operation.allowSceneActivation = true;
    }
}

progressが0.9で止まる理由

読み込み開始 → progressが0.9に到達 → 切り替えを許可
allowSceneActivationがfalseの間は、progressは0.9で止まり、isDoneもfalseのままです。trueに戻すとシーンの切り替えが進みます。

コードのバーはprogressを0.9で割り、切り替え準備までを0~100%で示しています。残り時間の割合ではありません。小さいシーンでは一気に進むこともあります。表示用の待ち時間や偽の進捗は加えていません。

ロード中にSceneControllerを無効化・破棄すると、切り替え許可まで進めなくなるため、そのまま有効にしておきます。また、切り替えを保留したまま別のUnloadSceneAsyncなどを待つと、処理が詰まる場合があります。詳しくはallowSceneActivationの公式仕様を確認してください。

ロード画面を表示してから切り替える演出例。動画の待ち時間はデモ用で、上のコードは読み込み完了後に切り替えます。

現在のシーン名を取得する:GetActiveScene

CurrentSceneLogger.csを有効なGameObjectに付けると、再生開始時にアクティブなシーン名をConsoleへ出せます。

using UnityEngine;
using UnityEngine.SceneManagement;

public class CurrentSceneLogger : MonoBehaviour
{
    private void Start()
    {
        Debug.Log("現在のシーン名: " +
            SceneManager.GetActiveScene().name);
    }
}
Consoleへ現在のシーン名を表示する例。

Additiveで複数のシーンを読み込んでいる場合、アクティブなシーンはそのうちの1つです。このGameObjectが所属するシーンを知りたい場合は、gameObject.scene.nameを使います。

シーンをまたいでデータを保持する

プレイ中だけ残す:DontDestroyOnLoad

スコアやBGMをシーン切り替え後も残したいときは、管理用GameObjectをDontDestroyOnLoadで保持します。親を持たないルートのGameObjectにGameManager.csを付けてください。子オブジェクトも一緒に残ります。

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }
    public int playerScore;

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    private void OnDestroy()
    {
        if (Instance == this) Instance = null;
    }
}

切り替え先にもGameManagerがある場合は、後から作られた重複オブジェクトを破棄します。Instanceが存在することを確認したうえで、GameManager.Instance.playerScoreから値へアクセスできます。

staticのInstanceは保持したGameManagerを参照する入口です。staticを付けるだけで、シーン内のGameObjectが破棄されなくなるわけではありません。切り替え前のUIなどへの参照は無効になることがあるため、次のシーンの参照へつなぎ直します。

ゲームを終了しても残す:PlayerPrefs・JSON

DontDestroyOnLoadは実行中のオブジェクトを保持する機能で、ディスクへの保存ではありません。次回起動時も残したい音量・設定値にはPlayerPrefs、まとまったセーブデータにはJSONなどを使い分けます。

PlayerPrefsはUnityの公式APIです。int・float・stringを保存できますが、暗号化されないため機密情報の保存には向きません。保存先と実装例はUnityのセーブデータの保存場所と作り方で紹介しています。

今のシーンを残して追加する:Additive

共通UIとステージを分けたいときは、LoadSceneAsyncの第2引数へLoadSceneMode.Additiveを指定します。追加で読み込むため、既存のシーンは残ります。自動で高速化されるわけではなく、重ねたシーンのメモリや処理負荷も増えます。

追加先は自動でアクティブシーンにはなりません。生成先やライティング設定の基準を切り替えたい場合は、ロード完了後にSetActiveSceneを呼びます。使い終えたシーンはUnloadSceneAsyncで閉じます。カメラ・AudioListener・EventSystemの重複にも注意しましょう。

うまく切り替わらないときの確認点

シーンが見つからない・ボタンを押しても動かない

まずConsoleのエラーを読み、シーン名とビルド対象のチェックを確認します。ボタン側ではOn Click()の対象GameObjectと関数、スクリプトのコンパイルエラー、EventSystemの有無を順に見てください。

ロード画面が消えない・進捗が止まる

allowSceneActivationをfalseのままにしていないか、ロード用GameObjectが無効になっていないかを確認します。Time.timeScaleを0にしている場合、WaitForSecondsは進みません。この例はフレーム単位で待つため、その待ち時間を入れていません。次のシーンを通常速度で動かすなら、ポーズ処理側でtimeScaleを戻すことも忘れないでください。

データが消える・メモリが減らない

データが消えるときは、保持するGameObjectがルートにあり、切り替え先の初期化で値を上書きしていないかを確認します。DontDestroyOnLoadとセーブ処理は目的を分けて使いましょう。

UnloadSceneAsyncはシーン内のGameObjectを破棄しますが、使用していたアセットのメモリをすべて解放する処理ではありません。Resources.UnloadUnusedAssetsは未使用アセットを解放するAPIです。Singleのシーンロードでは自動で呼ばれるため、毎回追加で呼ぶコードを入れる前にProfilerで必要性を調べましょう。

まとめ

最初はシーン登録 → ボタン接続 → LoadSceneの順で確認すると、問題を切り分けやすくなります。ロード画面が必要になったらLoadSceneAsyncへ進み、データは「プレイ中だけ残すか、次回起動にも残すか」で保存方法を選びましょう。

参考:Unity公式ドキュメント

LoadSceneAsync / DontDestroyOnLoad / PlayerPrefs / SetActiveScene / UnloadSceneAsync / UnloadUnusedAssets