スクリプトでシーンを切り替えよう【Unityスタートガイド8-3】

タイトル画面でボタンを押したらゲームが始まり、ゲームが終わったらリザルト画面へ。シーンを分けて作ったら、次はスクリプトでシーンを切り替えましょう!今回は、SceneManagerを使った切り替え方と、シーンをまたいでスコアなどを持ち越す方法をご紹介します。

目次

この記事で学べること

  • SceneManager.LoadSceneでシーンを切り替える方法
  • シーン名と番号(インデックス)での指定のしかた
  • キー入力やボタンでシーンを切り替える方法
  • 少し待ってから切り替える方法(コルーチン)
  • DontDestroyOnLoadで、シーンをまたいでデータを持ち越す方法
  • 読み込み中の進み具合を取る方法(LoadSceneAsync)

前提知識

この記事ではボタン(UI)も少しだけ使います。UIの詳しい作り方は第9章で説明します。

準備:シーンを2つ用意する

  1. 8-1の手順で「TitleScene」と「GameScene」を作り、Scenesフォルダに保存
  2. File>Build Profilesを開き、Scene Listに2つとも追加(TitleSceneを一番上=0番にする)

Scene Listに入っていないシーンは、スクリプトから読み込めません。ここが一番のつまずきポイントです。

SceneManager.LoadSceneの基本

シーン名で切り替える

using UnityEngine;
using UnityEngine.SceneManagement; // シーンを扱うために必要

public class SceneLoader : MonoBehaviour
{
    [SerializeField] private string sceneName = "GameScene";

    // シーンを切り替える
    public void LoadTargetScene()
    {
        SceneManager.LoadScene(sceneName);
    }
}
  • using UnityEngine.SceneManagement;を書かないと、SceneManagerが使えません
  • 切り替え先のシーン名はInspectorで変えられるようにしておくと、1つのスクリプトをいろいろな場所で使い回せます
  • publicのメソッドにしておくと、次に説明するボタンからも呼べます

番号(インデックス)で切り替える

Scene Listの上から0、1、2…の番号でも指定できます。

SceneManager.LoadScene(0);   // Scene Listの一番上のシーン

シーン番号の確認方法:

  1. File>Build Profilesを開く
  2. Scene Listで、各シーンの右に表示されている番号を確認

基本はシーン名で指定するのがオススメです。番号はScene Listの順番を入れ替えるとずれてしまいます。

今のシーンをやり直す

// 今のシーンを読み込み直す(リトライ)
SceneManager.LoadScene(SceneManager.GetActiveScene().name);

キーを押して切り替える

まずはUIを使わずに、Spaceキー(Jump)でタイトルからゲームへ進んでみましょう。

using UnityEngine;
using UnityEngine.SceneManagement;

public class TitleInput : MonoBehaviour
{
    [SerializeField] private string nextSceneName = "GameScene";

    private InputSystem_Actions inputActions;

    void Awake()
    {
        inputActions = new InputSystem_Actions();
    }

    void OnEnable()
    {
        inputActions.Enable();
    }

    void OnDisable()
    {
        inputActions.Disable();
    }

    void Update()
    {
        if (inputActions.Player.Jump.WasPressedThisFrame())
        {
            Debug.Log(nextSceneName + "に切り替えます");
            SceneManager.LoadScene(nextSceneName);
        }
    }
}

TitleSceneに空のオブジェクトを作ってアタッチし、再生してSpaceキーを押すと、GameSceneに切り替わります。

ボタンで切り替える

ボタンを作る

  1. TitleSceneで、Hierarchyで右クリック>UI>Button – TextMeshPro
    • 初めてTextMeshProを使うときは「TMP Importer」のウィンドウが出るので、Import TMP Essentialsを押す
  2. Canvas・Button・EventSystemが一緒に作られる
  3. Buttonの子にある「Text (TMP)」を選び、文字を「ゲーム開始」に変える

スクリプトをアタッチする

  1. 空のオブジェクトを作り、名前をSceneLoaderにする
  2. 上のSceneLoaderスクリプトをアタッチし、Scene Nameを「GameScene」にする

ボタンのOn Clickに登録する

  1. Buttonを選び、InspectorのButtonコンポーネントにあるOn Click ()の「+」を押す
  2. 出来た欄(None (Object))に、HierarchyのSceneLoaderオブジェクトをドラッグ&ドロップ
  3. 右のドロップダウン(No Function)からSceneLoader>LoadTargetSceneを選ぶ

再生してボタンをクリックすると、GameSceneに切り替わります。On Clickに出てくるのは、publicで、引数がない(または1つの)メソッドだけです。

少し待ってから切り替える:コルーチン

「ボタンを押したら効果音を鳴らして、1秒後に切り替える」のように、待ってから続きをする処理はコルーチンで書きます。

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

public class DelayedSceneLoader : MonoBehaviour
{
    [SerializeField] private string sceneName = "GameScene";
    [SerializeField] private float delay = 1f;   // 待つ秒数

    private bool isLoading = false;

    // ボタンのOn Clickから呼ぶ
    public void LoadWithDelay()
    {
        if (isLoading)
        {
            return;   // 連打しても1回だけ
        }

        isLoading = true;
        StartCoroutine(LoadAfterDelay());
    }

    IEnumerator LoadAfterDelay()
    {
        Debug.Log(delay + "秒後に" + sceneName + "へ");
        yield return new WaitForSeconds(delay);   // ここで待つ
        SceneManager.LoadScene(sceneName);
    }
}

コルーチンの最低限の書き方:

  • 戻り値の型をIEnumeratorにする(using System.Collections;が必要)
  • yield return new WaitForSeconds(秒数);で、その秒数だけ待ってから続きを実行する
  • 呼び出すときはStartCoroutine(メソッド名());

Updateで時間を数える(7-3のTimedSpawnerのような)書き方でも同じことが出来ますが、「待ってから1回だけ何かする」ならコルーチンがシンプルです。

シーンをまたいでデータを持ち越す

シーンを切り替えると全部消える

シーンを切り替えると、前のシーンのオブジェクトは全部消えます。スクリプトの変数に入れていたスコアも、一緒に消えてしまいます。

DontDestroyOnLoadで残す

DontDestroyOnLoadを呼んだオブジェクトは、シーンを切り替えても消えずに残ります。

using UnityEngine;

public class GameSession : MonoBehaviour
{
    // どのシーンからでも GameSession.Instance で使えるようにする
    public static GameSession Instance { get; private set; }

    public int Score { get; private set; }

    void Awake()
    {
        // すでに別のGameSessionがあるなら、自分は消える(2つ目を作らない)
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public void AddScore(int points)
    {
        Score += points;
        Debug.Log("スコア: " + Score);
    }
}

TitleSceneに空のオブジェクト(名前はGameSession)を作り、このスクリプトをアタッチします。ほかのスクリプトからは、次のように使えます。

GameSession.Instance.AddScore(10);          // スコアを足す
Debug.Log(GameSession.Instance.Score);      // スコアを読む

ポイント

  • Awakeで呼ぶ:ほかのスクリプトがStartで使う前に準備しておくため
  • 一番上の階層(ルート)のオブジェクトに付ける:DontDestroyOnLoadは、親のいないオブジェクトにしか効きません。子オブジェクトに付けると、警告が出て残りません
  • 2つ目を消す:TitleSceneに戻るたびに新しいGameSessionが作られて増えていかないように、「もうあるなら自分は消える」処理を入れています
  • static:クラスそのものに1つだけある変数です。GameSession.Instanceのように、オブジェクトを探さなくても使えるようになります

6-10のCoinCollectorで、coinCount++;の代わりにGameSession.Instance.AddScore(1);とすれば、集めた枚数をリザルト画面まで持ち越せます。

読み込みの進み具合を取る:LoadSceneAsync

大きなシーンは、読み込みに時間がかかります。LoadSceneAsyncを使うと、読み込んでいる間もゲームを止めずに、進み具合を取ることができます。

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

public class AsyncSceneLoader : MonoBehaviour
{
    [SerializeField] private string sceneName = "GameScene";

    void Start()
    {
        StartCoroutine(LoadAsync());
    }

    IEnumerator LoadAsync()
    {
        AsyncOperation operation = SceneManager.LoadSceneAsync(sceneName);

        // 読み込みが終わるまで、毎フレーム進み具合を表示
        while (!operation.isDone)
        {
            // progressは0〜0.9で進むので、0〜1に直す
            float progress = Mathf.Clamp01(operation.progress / 0.9f);
            Debug.Log("読み込み中: " + (progress * 100f).ToString("F0") + "%");
            yield return null;   // 1フレーム待つ
        }
    }
}
  • yield return null;は「1フレーム待つ」という意味です
  • 進み具合をスライダーや文字で表示するには、第9章で説明するUIと組み合わせます
  • 小さいシーンだと一瞬で終わるので、ログが1〜2回しか出ないこともあります

注意点やヒント

スライム

シーンを切り替えたら、スコアが0に戻っちゃった・・・

フェアリー

シーンを切り替えると、オブジェクトも変数も全部リセットされるよ!残したいものはGameSessionのようにDontDestroyOnLoadで持ち越そうね

DontDestroyOnLoadは必要なものだけ

残したオブジェクトは、ゲームを終了するまでずっと残ります。何でも残していくと、使わないオブジェクトがどんどんたまっていきます。スコアや設定など、本当に持ち越したいものだけにしましょう。

よくあるエラーと対処法

エラー:The name ‘SceneManager’ does not exist in the current context

  • 原因:using UnityEngine.SceneManagement;が書かれていない
  • 対処法:スクリプトの先頭にusing UnityEngine.SceneManagement;を追加する

エラー:Scene ‘GameScene’ couldn’t be loaded because it has not been added to the active build profile…

  • 原因:シーンがBuild ProfilesのScene Listに登録されていない、またはシーン名のつづりが違う
  • 対処法:File>Build ProfilesのScene Listにシーンを追加する。スクリプトのシーン名と、Projectウィンドウのシーン名が同じか確認する

エラー:DontDestroyOnLoadしたのに消える

  • 原因:DontDestroyOnLoadを、親のいる子オブジェクトに使っている
  • 対処法:GameSessionを、Hierarchyの一番上の階層(どのオブジェクトの子でもない場所)に置く

エラー:GameSessionがシーンに戻るたびに増えていく

  • 原因:2つ目を消す処理(Instanceがすでにあるか調べる部分)がない
  • 対処法:上のサンプルのように、Awakeで「もうあるなら自分をDestroy」する

エラー:ボタンのOn Clickにメソッドが出てこない

  • 原因:メソッドがpublicではない、または引数が2つ以上ある
  • 対処法:public void メソッド名()のように、publicで引数のないメソッドにする

まとめ

  • using UnityEngine.SceneManagement;を書いて、SceneManager.LoadScene("シーン名")で切り替える
  • 切り替えられるのは、Build ProfilesのScene Listに登録したシーンだけ
  • ボタンのOn Clickには、publicのメソッドを登録する
  • 待ってから切り替えるならコルーチン(WaitForSeconds)
  • シーンをまたいで残したいものは、ルートのオブジェクトにDontDestroyOnLoad

これで第8章はおしまいです!タイトル、ゲーム、リザルトと、ゲームの流れが作れるようになりました。

次のステップ

次の第9章では、ボタンやスコアの表示など、画面のUIを本格的に作っていきます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次