シーンの遷移(Unity6)

開発Tips

シーンの遷移を行います。

用意したシーンは以下の3つ

タイトルのScene(TitleScene)、ステージのScene(StageScene)、マップのScene(WorldScene)です。

Build Profilesにセット

Scene遷移を行う際Build ProfilesのScene Listに登録されていないと使えないので追加しておきます。
0がアプリ起動時に呼び出されるSceneです。

File > Build Profiles
でDialogが開きます。追加したいSceneを開き「Add Open Scenes」を押します。

シーン遷移

とりあえず、Titleからシーン遷移してみます。PlayerInputで入力が取れることをこないだやったのでそのまま使ってみます。

ゲームコントローラー(Unity6)
キーボードでいろいろやっていくのが大変になったのでコントローラーの入力を実装していく。Window -> Package ManagerからUnity Registryにある「Input System」を確認、インストール済み(Ins...

マウスクリック、またはEnterから遷移するようにしたいのでOnAttackからできるようにする。コントローラーのXも攻撃に設定してあるので、それも反応してしまうけどまあ問題ないでしょう。

以下のスクリプトとPlayer Inputを同じオブジェクトに入れる

using UnityEngine;
using UnityEngine.SceneManagement;

public class TitleScript : MonoBehaviour
{
    private void OnAttack()
    {
        Debug.Log("OnAttack");
        CallScene();
    }

    private void CallScene()
    {
        SceneManager.LoadScene("StageScene");
    }

}

まあ、とりあえずStageSceneに遷移する。確認してみます。タイトル画面でEnterです。

切り替わりました。

パラメータを次のSceneに渡す

シーンの切り替え時に前のシーンで利用したデータを保持しておきたいものです。

よく見かけるのは、

  • グローバルな静的(static)変数
  • シングルトン
  • DontDestroyOnLoad
  • Playfabs
  • SceneManager.sceneLoaded
  • ScriptableObject

グローバルな静的(static)変数

グローバル変数の場合

public static class GlobalParameter
{
    public static int HP;
}

とすると、どこでもGlobalParameter.HPで呼び出せるので

//呼び出し元
private void CallScene() 
{
    GlobalParameter.HP = 300;
    SceneManager.LoadScene("StageScene");    
}

としておけば、呼び出し先のどのScriptからも、以下のように呼び出せます。

private void Start() {
    //HPバーの表示を更新するために値をセットする。
    this.HpValue = GlobalParameter.HP;
}

問題があります。ただの静的クラスのため、inspectorに表示されません。

初期値の管理が煩雑になりがちで、最初から通しでテストする分には問題ないと思いますが、途中のステージの部分からデバッグとなるとほしい値を取得するのが面倒です。

シングルトン(Singleton)

シングルトン(Singleton)は、クラスの動的に作成したインスタンス静的な変数に代入し、どこでもみれるようにするものです。利点としては、MonoBehaviourを継承することでHierarchyで初期値の編集が可能になることです。

ただし、グローバルな静的(static)変数同様、シーンが切り替わるとHierarchyからはいなくなります。
以下は例です。

public static class GlobalParameter : MonoBehaviour
{
  public static GlobalParameter instance;
    
    public int HP;

    private void Awake()
    {
        //シングルトンは重複してはいけないので、すでに呼び出し済みの場合は、新たな呼び出しはなかったことにする。
        if (instance != null && instance.gameObject != null)
        {
            Destroy(this.gameObject);
            return;
        }

	instance = this;
    }
}

このスクリプトをHierarchyのオブジェクトにセットすれば、動きます。GameManagerなどのオブジェクトが適切かと思います。

//呼び出し元
private void CallScene() 
{
    GlobalParameter.instance.HP = 300;
    SceneManager.LoadScene("StageScene");    
}

としておけば、呼び出し先のどのScriptからも、以下のように呼び出せます。

private void Start() {
    //HPバーの表示を更新するために値をセットする。
    this.HpValue = GlobalParameter.instance.HP;
}

DontDestroyOnLoad

これは、動的に作成したインスタンスをシーンの切り替わりで破棄しないようにします。

例えばStatusManagerというオブジェクトを作成します。これをシーン移動で削除しないようにします。StatusManagerオブジェクトに以下のスクリプトを設定します。

using UnityEngine;

public class StatusManagerScript : MonoBehaviour
{
    public int HP
    private void Awake()
    {
        // DDOLに登録する
        DontDestroyOnLoad(gameObject);
    }
    
    public void MoveToScene()
    {
        SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
    }
}

これでシーンを移動しても削除されないオブジェクトが作成されます。

シーンを移動したらDontDestroyOnLoadを解除したい場合は、遷移先のシーンで以下のようにします。これでオブジェクトはDontDestroyOnLoadから解放され、新しいシーンのオブジェクトの一つになります。

StatusManagerScript status = GameObject.Find("StatusManager").GetComponent<StatusManagerScript>();
status.MoveToScene();

データの受け渡しが済んで、用がなくなったDontDestroyOnLoadのオブジェクトを削除したい場合は、Destroyしてあげます。

GameObject statusObject = GameObject.Find("StatusManager");
Destroy(statusObject);

PlayerPrefs

これはシーン遷移用ではなくて、セーブ/ロード用アプリの外、xmlファイルなどの外部ファイルにデータを保存して、シーンが切り替わったときに読み込んでデータを取得するといった手法です。
using UnityEngine;
で利用できるようになるので簡単です。

オートセーブを実装しているならこの方法でもいいかもしれない。
シーンの切替が頻繁になる場合は、セーブ/ロードの時間が長くならないような対策は必要かと思う。

これも問題があって、Windowsの場合はレジストリに保存するんです。個人的にレジストリはなるべく汚したくないので、セーブ/ロードを行う際には別の手段を考えていきたいと思います。

あとファイルやレジストリに出力するということは改ざんのリスクもあるということです。

使い方は、簡単です。保存は以下です。

// キーと値をセットする
PlayerPrefs.SetInt("HP", 100);
PlayerPrefs.SetFloat("Weight", 3.333f);
PlayerPrefs.SetString("Character Name", "sample name");
 
// 保存
PlayerPrefs.Save();

ロードは以下のようにします。初期値を設定しています。キーがなければ第2引数の値が設定されます。初期値は必須ではありません。

// キーを指定して値を読み込む
int hp = PlayerPrefs.GetInt("HP", 100);
float weight = PlayerPrefs.GetFloat("Weight", 1.0f);
string char_name  = PlayerPrefs.GetString("Character Name", "No Name");

SceneManager.sceneLoaded

今回はこれで行おうと思います。今までのものは、変数などに設定したものを自分から取りに行くパターンでしたが、これは元のシーンから次のシーンへ値を渡すものになります。

利点としてシーンの遷移で元のシーンと次のシーンでの依存度が低いということです。
つまり、これまでのやりかただと元シーンのパラメータに不備があったり、参照できないってなると次のシーンではエラーになりがちです。

このやり方だと次のシーンに値を渡して元のシーンはいなくなる。つまりそれぞれのシーンは、自分以外のものに依存していないのでエラーが起こりにくいということです。

using UnityEngine;
using UnityEngine.SceneManagement;

public class PortalScript : MonoBehaviour
{
    private int HP;

    void OnTriggerEnter(Collider other)
    {
        if (other.tag != "Player") return;

        //Playerがポータルにあたったらシーン遷移
        CallSceneChange(other.gameObject);
    }

    private void CallSceneChange(GameObject player)
    {
        //*** この関数が呼び出されるのは、まだ遷移元のシーン ***//

        //シーンのロードが完了したときに呼ばれるイベント関数を設定
        SceneManager.sceneLoaded += NextSceneLoaded;

        //キャラクターのステータスを取得(引継ぎ元の取得)
        StatusComponent status = player.GetComponent<StatusComponent>();
        HP = status.HpValue;

        //次のシーンの呼び出し
        SceneManager.LoadScene("WorldScene");
    }

    private void NextSceneLoaded(Scene nextScene, LoadSceneMode mode)
    {
        //*** この関数が呼び出されるのは、もう遷移先のシーン ***//

        //次のシーン遷移で実行されないようにイベント関数を消しておく
        SceneManager.sceneLoaded -= NextSceneLoaded;

        //新しいシーンのGameObjectを取得する(引継ぎ先の取得)
        GameObject player = GameObject.Find("Player");
        StatusComponent status = player.GetComponent<StatusComponent>();
        
        //パラメータ引継ぎ
        status.HpValue = HP;
    }
}

ちょっとわかりにくいですが、遷移できました。

ScriptableObject

ScriptableObjectは、ゲーム中はデータを参照するだけのものだと思っていたのでちょっと違和感があります。

どうやらHierarchyにないため、変更をかけるとシーンを変えても値が変わったままになるのを利用して行うようです。ただデバッグ終了後も変わったままになってしまうので、初期化する処理が必要みたいです。

以下にQiitaの記事がありました。今回は取り扱いしません。

UnityのScene間でのデータの受け渡し方法について比較する - Qiita
この記事はUnity Advent Calendar 2021 その2 20日目の記事です Unityにおいて何かしらのデータの値をSceneを超えて渡したい場合(例えばインゲームシーンでのスコアをアウトゲームシーンで結果発表のために使いた...

適材適所

シーン遷移に限ったことではないが、今回いろんなパターンを記述したどれか一つしか使ってはいけないということはなくて、状況に応じて複数のパターンを利用するのがいいと思う。

  • 特定のシーンでしか使わないというパラメータは、SceneManager.sceneLoadedを使う
  • 最初から最後まで通して使うというのであれば、グローバルな静的(static)変数やシングルトン、DontDestroyOnLoadなどを利用する
  • オートセーブ機能があってシーン遷移でセーブするというのであればPlayerPrefsまたは、ファイルへの読み書きを行えばよいかと思います。

コメント

タイトルとURLをコピーしました