i-school - 2DタイルマップRPG 手順15
 敵とのランダムエンカウントの発生方法の実装を行います。実際の戦闘シーンはありません。
2回の手順に分けて実装を行っていきます。下記の実装動画は最終的な目標実装になります。


<実装動画>
動画ファイルへのリンク


手順15 −ランダムエンカウントの実装−
25.GameData スクリプトと EncountManager スクリプトを作成し、PlayerController スクリプトを修正して、プレイヤーキャラの移動に際してランダムなエンカウントを発生させる処理を実装する
26.Battle シーンを作成し、ランダムエンカウント時に、Main シーンから Battle シーンへと遷移する処理を実装する


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

 ・Vector2.magnitude 変数
 ・引数を利用して処理の振る舞いを変更する実装例
 ・引数を利用して外部のクラスの情報をスクリプト内に取得する方法の実装例
 ・Debug.Log メソッドの活用方法



25.GameData スクリプトと EncountManager スクリプトを作成し、PlayerController スクリプトを修正して、プレイヤーキャラの移動に際してランダムなエンカウントを発生させる処理を実装する

1.設計


 まずは最初に、プレイヤーキャラが移動している間に抽選を行い、ランダムなタイミングで敵とのエンカウントを発生する制御処理を実装します。
実際に敵とのバトルではなく、Debug.Log メソッドを利用して、エンカウントが発生しているかを確認します

 それが完成したら次は、エンカウントと同時に別のシーンへと遷移する処理を実装します。ここまでをこの手順で扱います。
次の手順ではバトル終了後に再度、Main シーンの元に位置に戻る処理などを実装していきます。

 シーンの遷移を伴う処理になりますので、シーンの遷移後にも利用したい情報がある場合にはいくつか方法があります。
今回は、DontDestroyOnLoad メソッドを実装しているスクリプトがアタッチされているゲームオブジェクトに必要な情報を保持するように設計します。

 そうすることで、別のシーンに遷移をした後でも、前のシーンの情報を活用できる処理を実装できるようになります。


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


 シーンの遷移を行う前に、必要な情報を保持するためのクラスを作成します。
ゲーム内で利用する色々な情報を設定・管理するようにしたいので、GameData という名前にしておきます。

 DataBaseManager スクリプトと同じように、シングルトンデザインパターンを利用して作成を行います。
合わせて DontDestroyOnLoad メソッドも実装することで、GameData スクリプトがアタッチされているゲームオブジェクトを
シーンの遷移によっては破棄されないゲームオブジェクトとして振る舞えるようにします。

 今回はまず、エンカウントが発生する確率の設定値と、エンカウントが発生している状態かどうかを判定するための値を変数として用意します。
また、デバッグ作業を行いやすくするためにデバッグ機能を次に作成する EncountManager クラス内に用意しますので、その機能を有効にするための変数も用意します。


GameData.cs

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


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


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


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

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

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


ヒエラルキー画像



インスペクター画像



 isDebug 変数のスイッチにチェックを入れて true の状態にしてください。
このスイッチがオンのときだけ、エンカウント発生時のデバッグ操作を有効にします。


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

 一度、ゲームを実行してください。
ヒエラルキーの Main シーンではなく、下段に DontDestroyOnLoad というシーンと同じアイコンの場所が増えます。
この中に、DataBaseManager ゲームオブジェクトと一緒に GameData ゲームオブジェクトも存在していれば、
シーン遷移しても破棄されないゲームオブジェクトとして存在出来ています。


ヒエラルキー画像



 この状態はゲームを実行したときにだけ確認できます。従来は、Main シーン内に存在していれば問題ありません。


4.EncountManager スクリプトを作成する


 エンカウント関連の処理を管理するためのクラスを1つ作成して管理を行うようにします。

 ここでは PlayerController クラスに EncountManager クラスの情報を渡す処理と、
プレイヤーが移動した際に一定の確率でエンカウントの発生を行う処理を実装します。

 また、一度エンカウントが発生すると、その後はエンカウントを発生させない制御も実装しています。
ただしこれは、実際のゲーム内において必要な制御であり、デバッグの際にもこの制御が働いてしまうため、
繰り返しエンカウント発生のデバッグ作業を行う際には不便な状態を引き起きします。

 こういったケースでは、デバッグのための機能を一緒に実装しておきます
今回の場合であれば、エンカウントを発生後に指定した特定のボタンを操作することでこの制御をオフにする機能を実装しています。
そうすることで再度エンカウントを発生させることが出来るようになりますので、逐一ゲームを再実行する必要がなくなります。

 このようにデバッグしやすい環境を自分で用意しておくことで、一回のプレイで何回でもエンカウントを発生が正常に行われているか繰り返しの確認が行えるようになるため、
デバッグをスムースに行うことができます。他のデバッグの際にも有用な方法ですので覚えておいてください。
ポイントとしては、どのような機能があればデバッグがやりやすくなるだろうか、という部分で処理を見ていくことにあります。

 なおスクリプト完成時点では、Start メソッドで呼び出す PlayerController クラス側の処理が未実装であるため、必ずエラーが発生します
そのため、この部分以外のエラーがなくなったら、一旦セーブをしてから、PlayerController クラスの修正を行います。
そちらに該当するメソッドを実装することで、EncountManager クラス側のエラーも解消されます。


EncountManager.cs

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


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



 GameData.instance.randomEncountRate の値が大きいほど、乱数の幅が広がります。


 また encountRate の値が大きい程、エンカウント発生率が上がります。
 
 そのため、GameData.instance.randomEncountRate の値を小さくし、かつ、encountRate の値が大きければ高確率でエンカウントが発生する仕組みです。

 このエンカウント発生判定を行う JudgeRandomEncount() メソッドは、FixedUpdate メソッドから実行される予定です。
そのため、1秒間に50回判定が発生する予定ですので、現在の数値設定ですと、高確率でエンカウントが発生します。
これはデバッグ用に GameData.instance.randomEncountRate の値を低くしているためですので、確認作業が終わったら調整してください。


5.PlayerController スクリプトを修正して、移動に合わせてエンカウントが発生する処理を実装する


 EncountManager クラスより呼び出される命令用のメソッドを作成します
このメソッドの引数を通じて、EncountManager クラスの情報を取得して、変数に代入する処理を行います。
これにより、外部からのクラスの情報を PlayerController クラス側に届けてもらうようにしています。

 private 修飾子で宣言している変数については、GameObject.Find メソッドを利用する以外にも、
このようにメソッドを利用することで必要な情報を取得することが出来ますので覚えておいてください。
 


 プレイヤーの移動している状態を Rigidbody2D コンポーネントの magnitude の値を利用して判定します。
この magnitude の値は float 型でベクトルの長さを取得できます。

参考サイト
Unity公式スクリプトリファレンス
Vector2-magnitude
https://docs.unity3d.com/ja/2019.4/ScriptReference...


 そのため、magnitude の値が 0 よりも大きければ、このゲームオブジェクトは移動している状態であることが分かります。
値が 0 であればこのゲームオブジェクトは移動していない状態であることがわかります。
これを利用して、プレイヤーが移動している状態であるか、停止している状態であるかを判定します

 ただし、キャラのゲーム画面での移動は magnitude の値 0 以上であってもすぐには移動を開始しません。
ある程度の値にならないと動いているように見えないためです。
そこでこの判定には 0 ではなく、新しく movementThreshold 変数を作成し、この値を閾値(しきいち)として利用し、この値以上であるときに「移動中」と判断させます。
例えばこの値を 0.35f とした場合、magnitude の値が 0.35f 以上になっていれば「移動している」と判断するために利用します。

 移動している状態に限り、EncountManager クラスに用意した JudgeRandomEncount メソッドを継続的に呼び出します
このとき、1回だけではなく、移動に応じて継続的に JudgeRandomEncount メソッド呼び出すため、
プレイヤーが移動するたびに、エンカウントが発生したかどうかを判定する設計になっています。


PlayerController.cs

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


 スクリプトを修正したらセーブをします。
合わせて、EncountManager スクリプトのエラーも解消されます。


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


 すべてのスクリプトのエラーが解消されたので、スクリプトをゲームオブジェクトにアタッチ出来るようになります。
これは、アタッチしたいスクリプト自体にエラーがないとしても、いずれかのスクリプトでエラーがある場合にはアタッチできない状態ですので注意してください。



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

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

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


ヒエラルキー画像



インスペクター画像



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


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


 処理を実装しましたので、デバッグを行います。
デバッグを行う際には、処理の流れを把握し、どの部分が正常に制御されていればよいかを認識してから始める必要があります。
ゲーム画面でのみ確認を行うことは危険です。必ず、内部的な処理にも目を凝らしてください。

 これはエラーがあった場合に原因を特定する際に役に立てるためです。
表面的なゲームの動きはすべて内部的な処理によって制御されていることを理解していくことができれば、
自ずとエラーをみつけることも出来るようになります。内部の理解が難しいとエラーを見つけることも困難になります。

 デバッグを行う際には特に、ゲームが動いているから大丈夫、という考え方は払拭してください。



 今回確認するべき点は2か所あります。

 まずはプレイヤーが移動している中で、ランダムエンカウントが発生するかどうかです。
この処理は、 PlayerController クラス内の Move メソッド内に追加した処理と、
その中で呼び出される EncountManager クラスの JudgeRandomEncount メソッドとが連動している必要があります。
連動していればちゃんと処理が実行されて、移動しているうちに必ずどこかのタイミングでエンカウントが発生します。

 エンカウントが発生するとConsole ビューに "エンカウント : 0 " と表示されます
またこのときには GameData クラスの情報も合わせて確認を行います。

 JudgeRandomEncount メソッド内で GameData クラスの isEncount 変数を true に切り替えて、エンカウント発生状態を作り出しています。
この情報はインスペクターより確認することが出来ますので、エンカウント発生した場合には、GameData ゲームオブジェクトのインスペクターより
GameData クラスの isEncount 変数にチェックが入って true の状態になっているかどうかを確認をしてください。

 どちらも問題がなければそのまま移動してください。
この isEncount 変数が true の間はエンカウントが発生しない状態になっているはずですので、
いくら歩いても Console ビューには エンカウントの表示が出ないことを確認します。



 2つ目に GameData クラスの isDebug 変数が true である場合には、isEncount 変数を false の状態に切り替えることが出来ます。
指定されているキーボードのキーを入力してください。Console ビューに "エンカウント終了" と表示されればデバッグ機能も正常に動作しています。

 再度移動すると、またエンカウントが発生するようになっていますので、そちらも確認をしてください。
このデバッグを繰り返すことで、どの位の頻度でエンカウントが発生するのかも体感できるため、任意な状態に調整していくことが出来ます。

 これが今回デバッグを行って確認をするべき処理の流れになります。


<実装動画>
動画ファイルへのリンク


 動画のように GameData の情報をインスペクター以外に表示するには、ヒエラルキーの中にある、情報を表示したゲームオブジェクトの上で右クリックをしてメニューを開き、
一番下にある Properties... を選択してください。選択したゲームオブジェクトの情報がポップアップ表示されますので見やすい位置に移動してください。


26.Battle シーンを作成し、ランダムエンカウント時に、Main シーンから Battle シーンへと遷移する処理を実装する

1.設計


 Debug によって、無事にエンカウント処理が発生していることがわかりました。
この手順では Battle シーンを作成して、Debug の部分をシーン遷移する処理へと変更していきます。

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

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

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


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


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

 シーン遷移の処理には using の宣言が必要になります。
各スクリプトでバラバラにシーン遷移の処理を書くのでは、using の宣言もその都度必要なりますし、
何よりも、どのスクリプトでシーン遷移を行っているのかを把握しにくくなり、管理が煩雑になります。

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

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

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

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



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

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

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


SceneStateManager.cs

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


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


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


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

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

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


ヒエラルキー画像



インスペクター画像



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


4.Battle シーンの作成を行う


 新しいシーンを1つ作成します。

 Scenes フォルダ内で右クリックをしてメニューを開き、Create => Scene を選択してください。
新しいシーンが作成されますので、名前を Battle に変更します。

 Main シーンを Ctrl + S キーを押してセーブしてください。
その後、Scenes フォルダ内の Battle シーンのアイコンをダブルクリックして、編集するシーンを Main シーンから Battle シーンに切り替えてください。


5.Build Settings にシーンを設定する


 作成したシーンは Build Settings の設定を行わないとシーンの遷移が実行できません

 Unity Editor の左にあるメニューより、File => Build Settings を選択します。
Build Settings ウインドウが開きますので、上段の Scenes In Build 内にシーン遷移したいシーンを登録します。
Scenes フォルダにあるシーンを順番にドラッグアンドドロップして登録してください

 ゲームを実行すると一番上のシーンから開始されますので、登録する順番には注意してください。
登録順を間違えた場合には、Scenes In Build 内にある移動したいシーンをドラッグアンドドロップすることで順番を入れ替えることが出来ます。


Build Settings ウインドウ 画像


 
 以上で設定は完了です。



6.EncountManager スクリプトを修正する


 TODO 機能を活用し、シーン遷移の処理を実装していきます。
SceneStateManager クラスもシングルトンクラスですので、変数に代入する必要はありません。
SceneType の情報は、SceneStateManager クラス内にある enum ですので、最初に SceneStateManager と記述することで利用できます。
例えば、SceneType に登録してある Battle という列挙子を指定する場合には、SceneStateManager.SceneType.Battle と記述して指定します。


EncountManager.cs

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


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


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


 ゲームを実行してプレイヤーキャラを移動させてください。
エンカウントと同時に Battle シーンへと遷移すれば、制御成功です。


<実装動画>
動画ファイルへのリンク


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

 次は 手順16 ーランダムエンカウント後のシーン遷移の実装ー です。