i-school - 3Dレールガンシューティング 発展12
 シーン遷移の処理について、新しい実装方法を学習します。
シーン遷移の処理専用のクラスを作成し、このクラスを利用することで、シーン遷移の処理を一元化して管理するようにします。

 この機能は すべてのゲームにおいて活用可能な汎用的な機能 になります。



発展12 ーシーン遷移機能ー

 新しい学習内容は、以下の通りです。

 ・enum のみのスクリプト
 ・シングルトンデザインパターンによるクラスの作成



1.設計


 シーンの遷移については、スクリプト内の using 宣言に UnityEngine.SceneManagement という宣言を追加することで実装出来ます。

 例えば、複数のスクリプトに using の宣言を作成して、そこからシーンの遷移を実行してもよいですが、
こういった設計の場合、どのスクリプトからシーンの遷移を実行しているのかわかりにくく、管理もしにくい状態 になります。
また、各スクリプト内にその都度 using の宣言も必要になりますので煩わしさも増えて しまいます。

 今回は今後シーンの数が増えることも想定して、シーンの遷移専用のクラスを作成して、そのクラス内ですべてのシーンの遷移の処理を管理する ようにします。
このスクリプトも シングルトンデザインパターンで作成しておく ことによって、すべてのスクリプトから変数の代入なく命令が出せる ように出来ますので 利便性が非常によくなります
また、DontDestroyOnLoad メソッドも一緒に宣言しておくことにより、シーンの遷移しても破壊されないゲームオブジェクトに出来ますので
常にゲームのシーン内に1つだけ存在し、いつでもどのスクリプトからでもシーン遷移の命令を実行することができる設計構造で実装することが出来ます。

 シーン遷移の処理は各シーンにおいて必要になる処理であるため、どのクラスからでもアクセスしやすく、いつでもシーンに存在しているクラスであることが望ましいです。
そういった観点から、今回は、シングルトンによる設計と実装を行います。


事前準備を行う


 シーン遷移の実装になりますので、2つ以上のシーンを用意してください。
今回の例では、Title シーンと Main シーンを作成し、Title シーンから Main シーンへとシーン遷移させる予定で設計しています。

 ボタン操作によってシーン遷移を行いたい場合には、Title シーンにはゲームスタート用のボタンを1つ配置してください。


Sceneビュー画像(参考画像。この通りである必要はありません)



 以上で事前準備は完成です。


2.SceneType スクリプトを作成する


 ゲーム内に利用するシーンの名称を登録するための enum を SceneType という名前で作成し、
シーンの名称を列挙子として登録しておきます。ここには、実際に自分が作ったシーンの名前をすべて記述しておきます。
シーンが増えたら、忘れずにここに追加をします。

 なお、列挙子は一見すると文字列のように見えますが、型としては string 型ではありません
そのため、列挙子の情報を文字列として利用したい場合には、string 型へのキャスト(型変換)が必要になります

 enum のみのスクリプトは、MonoBehaviour クラスの継承がありませんので、ゲームオブジェクトに依存しません。つまり、アタッチできません。
ですが、スクリプトを作成した段階で、すべてのスクリプト内で自由に宣言して利用することができます。


SceneType.cs

<= クリックすると開きます


 スクリプトを作成したらセーブします。


3.SceneStateManager スクリプトを作成する


 設計内容の復習になります。

 シーン遷移の処理には using の宣言が必要になります。
各スクリプトでバラバラにシーン遷移の処理を書くのでは、using の宣言もその都度必要なります。
何よりも、どのスクリプトでシーン遷移を行っているのかを把握しにくくなり、
また、異なるシーンに同じ内容のスクリプトを作成してアタッチする必要があったりと、管理が煩雑になります。

 こういった処理は 管理用のクラスを1つ作成し、それにすべて処理を制御させる設計を考えましょう

 今回のケースであれば、シーン遷移についてはすべてを担当するマネージャークラス という位置づけです。

 またこういった複数のスクリプトから処理が命令される可能性のあるクラスは、
以前に学習した シングルトンデザインパターンを利用したクラスとして作成しておく とよいです。

 シーン遷移の命令を実行するにあたり、変数への代入処理が不要になりますので、
どのスクリプトからでも命令を1行記述するだけで、シーン遷移の処理を実行できる設計が実装出来ます



 シーン遷移には SceneManager.LoadScene メソッドを利用しています。
このメソッドでは引数として文字列でシーンの名前を求められますが、これは事前に string 型の変数に文字列を代入しておいたり、
enum を作成してシーンの名前を列挙子として登録しておくことによって、文字列の打ち間違えを防ぐ ことが出来ます。
また、enum は ToString メソッドを利用することで、列挙子を同名の文字列へとキャスト(型の変換処理)可能 です。

 今回は enum で実装を行っていますので参考にしてください。enum は列挙子に登録した情報のみを対象としますので、
シーン遷移を行いたいシーンの名前のみを登録しておけば、存在しないシーンへの遷移も防ぐことができ、実行命令を記述する際にも見やすく、非常に便利です。

 つまりこれは、複数のメソッドを作成して、これは Main シーン用のメソッド、これは Start シーン用のメソッドという風にメソッド単位で分けるのではなく
1つのメソッドを作成して、引数の値によって遷移するシーンを分岐させるという設計になっています。


SceneStateManager.cs

<= クリックすると開きます。


 スクリプトを作成したらセーブを行います。


4.<シングルトンデザインパターンによるクラスの作成>


 シングルトン とは、数多くある デザインパターン の1つです。
そのクラスのインスタンスが必ず1つであることを保証する デザインパターンのことを言います。

 SceneStateManager クラスでは、このシングルトンを採用しています。
つまり、ゲーム中を通じて、この SceneStateManager クラスが1つしか存在できないようになります。
実装例は複数ありますが、一番読みやすい方式で記述しています。



<シングルトンデザインパターンのクラスの作成方法>
public static SceneStateManager instance;  // クラス名と同名の型を static で宣言する

private void Awake() {
    if (instance == null) {
        instance = this;
        DontDestroyOnLoad(gameObject);
    } else {
        Destroy(gameObject);
    }
}



 ポイントは、自分自身の SceneStateManager 型を static 修飾子付きの instance 変数として宣言していることです。
この instance 変数が SceneStateManager クラス自身が代入された情報として利用することになります。

 Awake メソッドを利用して、instance 変数が null (空っぽ) である場合には、SceneStateManager クラス(this)を代入します。
次の DontDestroyOnLoad メソッドは Unity が用意しているメソッドで、引数に指定されたゲームオブジェクトはシーン遷移をしても破壊されてないゲームオブジェクトになります。
この DontDestroyOnLoad メソッドはシングルトンデザインパターンにする際に一緒に用いられることが多いです。(この DontDestroyOnLoad メソッド機能自体は、シングルトンには関係ありません。)

 そして instance 変数が null ではない場合、つまり、2つ目以降の複数の SceneStateManager クラスが存在する場合には、その SceneStateManager クラスのゲームオブジェクトを Destroy します。
この手順により、SceneStateManager クラスがアタッチされているゲームオブジェクトが常にヒエラルキー上に1つしか存在しない状態を作り出しています

 このシングルトンによってインスタンスが1つか生成されないことが保証されますので、
逆説的に考えると、この SceneStateManager クラスへの参照は、いずれのクラスからであっても変数を介さずに参照を行えるようになります。



 例えば、NonPlayerCharacter というクラスがあり、その NonPlayerCharacter クラスを持つゲームオブジェクトが5つあった場合、
どの」NonPlayerCharacter クラスであるかを確定できないと、対象となる NonPlayerCharacter クラスへは参照できません。
そのため、NonPlayerCharacter 型の変数を用意して、その変数へ参照したい NonPlayerCharacter クラスを代入することによって、
はじめて NonPlayerCharacter クラスの情報を扱うことができるようになります。これが情報を扱う際の基本的な処理になります。

 ですがシングルトンである SceneStateManager クラスの場合には、このインスタンスは常に1つしかないことが保証されていますので、
どの」という指定の部分が不要になります。よってクラスの特定ができているため、変数への代入が不要になります。
SceneStateManager という指定はすなわち、自動的にただ1つの SceneStateManager クラスの参照が行われることになるためです。

 この機能を利用して SceneStateManager クラスを作成しておくことで、どのクラスからでも参照しやすい設計にしておきます。


<SceneManagement.SceneManager.LoadSceneAsync メソッド>


 SceneManager クラスのメソッドの1つです。シーン遷移を非同期処理として実行します。
戻り値は AsyncOperation 型になりますので、AsyncOperation 型の変数に代入することにより、読み込み状態の進捗を確認することが出来ます。


 // 非同期処理でシーンの遷移実行(現在実行しているシーンのバックグラウンドで次のシーンの読み込みを事前に行う)
  async = SceneManager.LoadSceneAsync(nextSceneName);

Unity公式スクリプトリファレンス
SceneManagement.SceneManager.LoadSceneAsync
https://docs.unity3d.com/ja/current/ScriptReferenc...



5.SceneStateManager ゲームオブジェクトを作成し、SceneStateManager スクリプトをアタッチする


 SceneStateManager スクリプトをアタッチするためのゲームオブジェクトを作成します。

 ヒエラルキーの空いている場所で右クリックをしてメニューを開き、Create Empty を選択します。
新しいゲームオブジェクトが作成されますので、名前を SceneStateManager に変更してください。

 その後、SceneStateManager スクリプトを SceneStateManager ゲームオブジェクトにドラッグアンドドロップしてアタッチしてください。


インスペクター画像



 以上でこのゲームオブジェクトの設定は完了です。


6.シーン遷移の処理の実装に挑戦する


 SceneStateManager ゲームオブジェクトに SceneStateManager スクリプトをアタッチしていますので、
いずれのスクリプトからでも、事前に変数への代入や、GetComponent メソッドを行う必要なく、シーン遷移の処理を実装出来ます。

 実装する場合には、以下の命令文を記述します。


<Main シーンへ遷移する場合>
 SceneStateManager.instance.PreparateNextScene(SceneType.Main);

 引数に設定した SceneType の列挙子のシーンへと遷移する命令になります。
ここでは、SceneType.Main を引数にしていますので、Main シーンへの遷移を行う命令になります。

 この引数の部分を変更することで、1つのメソッドを異なるシーンへの遷移用に使い分けることが出来ます

 SceneStateManager の PreparateNextScene メソッドを使ってシーン遷移する処理へと変更するように、自分でスクリプトを書いて実装に挑戦してみてください。
例えば、ボタンを押したらシーン遷移する、マウスで画面をクリックしたら遷移する、という風に、どんなタイミングでシーン遷移をすればいいのかを設計してみましょう。


7.ゲームを実行して動作を確認する


 タイトルシーンからメインシーン、そしてメインシーンからタイトルシーンへの遷移が行われるようになっているかを確認してください。

 SceneStateManager ゲームオブジェクトはシングルトンクラスです。そのため、複数のシーンに配置されていたとしても、必ず1つだけになるように設定されています。
デバッグのことを考えるならば、この SceneStateManager ゲームオブジェクトをプレファブにし、各シーンに配置しておくことをお勧めします。

 いずれのシーンからでもシーン遷移の処理を確認できますし、シーン遷移を行った先のシーンに SceneStateManager ゲームオブジェクトが存在していたとしても、
そちらは自動的に破壊されて常に1つだけが存在するようになるため、不具合を発生される恐れもないためです。

 なるべく効率のよいデバッグの方法を考えて実践していくといいでしょう。


8.<応用>


 シングルトンによるクラスは他のクラスにも設計して利用することができます。

 ・ゲームシーンで取得した情報を別のシーンでも利用したい
 ・異なるクラスから情報を共有して参照したい(SceneStateManager と同じ使い方)

 ステージ選択シーンでキャラを一人選択する、という想定でシーン遷移後にゲームが開始される場合、
選択したキャラの情報を代入した変数があるゲームオブジェクトはシーン遷移時に破棄されてしまいます。
その際、シングルトン+DontDestroyOnLoad メソッドのあるクラスに選択したキャラの情報を管理させることで
シーン遷移を行っても情報を失うことなく、次のシーンで選択したキャラの情報を利用できます。

 BGM や SE の再生、エフェクトの再生なども、それぞれ1つの管理クラスを作成して、そのクラスをシングルトンにしておくことで、
他のすべてのクラスから参照が行いやすくなります。
 SoundMaanger クラスを作り、PlayBGM メソッドを用意しておくことで、このメソッドをいずれのクラスからでも呼び出すことが出来れば、
SoundMaanger クラスを変数への代入処理を行わなくても、1行の命令文で BGM を変えることが出来ます。
 SE も同様です。敵やプレイヤーのクラスに SE を設定するのではなく、SoundMaanger クラスから SE 再生用のメソッドを実行して
適切な SE を再生してもらう、という処理が実現できます。



 以上でこの手順は終了です。

 => 次は 発展13 ーシーン遷移時のフェード機能ー です。