Unityでシーンを遷移する際、前のシーンにあったものはすべて破棄され新しいシーンがロードされる。ただ、シーンをまたいで値を保持したい局面はとてもよく遭遇する。今回はこのシーンをまたいで値を保持する様々な方法を実行例を交えながら考察していく。
準備
では実際に考察に入る前にいつものように簡単なプロジェクトを作成して一緒にやっていこう。

Scene1というシーンを新たに作成する

Scene1を開いてCubeを原点に作成。名前をCube1とする

Cube1スクリプトの作成
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Cube1 : MonoBehaviour
{
int num = 0;
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
num++;
Debug.Log(num);
}
if (Input.GetKeyDown(KeyCode.RightArrow)) {
SceneManager.LoadScene("Scene2");
}
}
}
作成したCube1スクリプトをCube1にアタッチ

いざ実行
実行して、上矢印キーを何回か押してみよう。コンソールに数字が出力されることがわかる。確認したら右矢印を押してみよう。まだシーンを作成してないのでエラーになることがわかる。

Scene2の作成
それでは新たにScene2を作成していこう。

新規CubeからCubeを作成してCube2にリネーム後ポジションを変更する

Cube2スクリプトの作成
以下のようにCube2スクリプトを作成し、Cube2にアタッチする
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Cube2 : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftArrow)) {
SceneManager.LoadScene("Scene1");
}
}
}
シーンの登録
さて2つのシーンはできたがこのままでは遷移できない。シーンの登録を行おう。上部ファイルメニューからBuildSettingsを開く

Scene1とScene2をドラッグで登録する

いざ実行!
実行し、左右矢印キーでシーンの遷移を行ってみよう。2つのキューブが交互に表示されればOKだ。
変数のチェック
ここからが本番だ。まずはシーン1に移動し上矢印を数回押す。

押した回数分数が増えていく。
シーン遷移!
ここで右クリックしてシーンを遷移した後、再びシーン1に戻って上矢印をおしてみよう。

さきほど4まで上がったはずなのにまた1からスタートしたことがわかる。
このようにシーンの遷移した場合、遷移前のシーンに存在しているすべてのインスタンスはゲームオブジェクトと共に破棄され、遷移後あらたに生成される。
シーンを遷移すると前のシーンで生成したインスタンスはゲームオブジェクトと共に破棄される
値を受け渡す
それではここでシーンを遷移した際の値の保持を考えてみよう。
具体的にはシーン1で値を作成し、それをシーン2で参照したい。
static
最初にやったようにインスタンスは破棄されるのでそのままでは値を受け渡すことができない。そこでまず考えれらるのがstaticフィールドとして保持したい値を作成することだ。
Cube1スクリプトの変更
さきほどまではインスタンスフィールドだった、numにpublic static をつけてstaticフィールドにしよう。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Cube1 : MonoBehaviour
{
public static int num = 0;
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
num++;
Debug.Log(num);
}
if (Input.GetKeyDown(KeyCode.RightArrow)) {
SceneManager.LoadScene("scene2");
}
}
}
Cube2スクリプトの変更
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Cube2 : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftArrow)) {
SceneManager.LoadScene("Scene1");
}
if (Input.GetKeyDown(KeyCode.DownArrow)) {
Debug.Log("num:"+Cube1.num);
}
}
}
下矢印を押したらnumを出力するように変更した。
いざ確認!
ここまで作成できたら実行してみよう。シーンを遷移してもnumの値はなくならずScene1.numでアクセスできることがわかる。しかも、シーン1に戻った際にも初期化されずに前回の数値を引き継いでいることがわかる。このようにpublic staticなフィールドを作成するとその変数にはどのシーンからでも
クラス名.変数名
でアクセスすることができる。しかもこの値はシーンが変わっても値を保持し続けることがわかる。
public static な変数にするとクラス名.変数名で同一シーンのほかクラスからアクセスできるのはもちろんのこと、シーン遷移があってもアクセスすることができる。
ゲームオブジェクトをシーンをまたいで存在させる
さきほどはフィールド変数の保持を行ったがこんどはゲームオブジェクトの保持を考えてみよう。最初にやったときのように基本はシーンの遷移とともにオブジェクトは破棄される。しかしDontDestroyOnLoadメソッドを使うことでシーンをまたいでも存在させることができる。さっそうやってみよう。
Cube1スクリプトの変更
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Cube1 : MonoBehaviour
{
public static int num = 0;
void Awake()
{
DontDestroyOnLoad(gameObject);
}
void Update()
{
if (Input.GetKeyDown(KeyCode.UpArrow)) {
num++;
Debug.Log(num);
}
if (Input.GetKeyDown(KeyCode.RightArrow)) {
SceneManager.LoadScene("scene2");
}
}
}
いざ実行!
これでこのオブジェクトがシーンの遷移によって消えることは無くなった。
Scene1から実行してScene2に遷移してみよう。

ヒエラルキービューにDontDestroyOnLoadという項目が追加されそこにCub1が登録されたことがわかる。実際のゲーム画面でもCube1は存在し続けていることがわかる。このようにDontDestroyOnLoadを使うとシーンの遷移によって破棄されないオブジェクトを作成することができる
DontDestroyOnLoadを使ってゲームオブジェクトを指定するとそのゲームオブジェクトはシーン遷移によって破棄されなくなる
が、しかし。。。
消えないことはわかったが、左右キーを押してシーンの移動を繰り返してみよう。

ゲーム画面を見ている限りは問題はないが実はとんでもないことが発生してる。
ヒエラルキーを見ればわかるが、Scene1に移動した際に新規Cubeを作成し、またDontDestroyOnLoadに登録してしまっている。確かに「DontDestroy(壊しませんよ。)」といっているが新たに作りませんよとは言ってない。せっかくシーンをまたいでゲームオブジェクトを保持できるようになったのだが、この増えていってしまう挙動は困る場合ある。
DontDestoryOnLoadは破棄しないというメソッド。新たにもう一つ作らないとは言ってないので注意。
シングルトンパターン
例えばGameManagerのようにゲームをしている間は常に存在していて欲しいが先程のように2つ以上生成されては困るゲームオブジェクトがある時がある、その場合はシングルトンパターンを使うとよい。シングルトンパターンといのはそのインスタンスが常に一つであることを保証するデザインパターンで今回のようなニーズにピッタリだ。
Cube1スクリプトの変更
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Cube1 : MonoBehaviour
{
public static Cube1 instance;//static 変数でCube1型のインスタンスを保持
int num = 0;//インスタンスフィールドに戻す(staticを外す)
void Awake() {
//初回のAwakeの時のみここがtrueになりインスタンスが登録される
if (instance == null) {
instance = this;//このインスタンスをstatic な instanceに登録
DontDestroyOnLoad(gameObject);
} else {
Destroy(gameObject);//2回目以降重複して作成してしまったgameObjectを削除
}
}
void Update() {
if (Input.GetKeyDown(KeyCode.UpArrow)) {
num++;
Debug.Log(num);
}
if (Input.GetKeyDown(KeyCode.RightArrow)) {
SceneManager.LoadScene("scene2");
}
}
//numのゲッターを作成しておく
public int GetNum() {
return this.num;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Cube2 : MonoBehaviour
{
void Update()
{
if (Input.GetKeyDown(KeyCode.LeftArrow)) {
SceneManager.LoadScene("Scene1");
}
if (Input.GetKeyDown(KeyCode.DownArrow)) {
//Cube1.instanceでシングルトンのCube1インスタンスにアクセス
Debug.Log("num:"+Cube1.instance.GetNum());
}
}
}
いざ、実行!
Scene1が選択されていることを確認してゲームを開始する。上で数が増えコンソールに表示される。右を押してシーンを遷移。Cube1が残っていることを確認すると共に下矢印でシングルトンインスタンスにアクセスして数が表示されることを確認する。
解説
public static Cube1 instance;
シングルトンという言葉を聞いてなにやら難しそうだと尻込みする必要はない。
最初にやったstaticを使ってインスタンスを登録するだけだ。こうしてやることでメモリ空間に一つだけこのインスタンスを保持することができるようになる。
int num = 0;
さきほどstaticに設定したフィールドをインスタンスフィールドに戻そう。
ゲーム期間中保持しておきたい値はこのようにインスタンスフィールドで管理していく。
void Awake(){}
インスタンス生成後、すぐに処理を行いたいのでAwakeで行う。(インスタンス生成はシーンがロードされてそのスクリプトがアタッチされているオブジェクトがヒエラルキーにあった場合、最初に行われる)
インスタンス生成->Awake->Start
if (instance == null) { instance = this;//このインスタンスをstatic な instanceに登録 DontDestroyOnLoad(gameObject); }
まずstatic で宣言したinstanceの状況を調べてnull(つまり初回)だったときのみ生成されたCube1インスタンスを登録する。と同時にDontDestroyを使って今作成された四角い形をしたゲームオブジェクト(Cube1)を破棄されないようにする
else { Destroy(gameObject);//2回目以降重複して作成してしまったgameObjectを削除 }
さきほどやったようにDontDestroyを使っても、シーンをロードする度にゲームオブジェクトを作成してしまう。なので2回目以降にロードされた場合はその重複して作成されるCubeを削除してあげればよい。
public int GetNum() { return this.num; }
numがprivateなのでpublicなゲッターを用意しておこう。(このクラスにプロパティを定義してもよい)
Cube2
Debug.Log("num:"+Cube1.instance.GetNum());
シングルトンで作成したインスタンスには
クラス名.staticフィールド名
でいつでもアクセスできる。
あとはそのインスタンスがメソッド実行するだけだ。
ゲーム実行中、ずっと情報を保持していたいオブジェクトがあった場合シングルトンを活用するとよい。
コメント