i-school - 2Dタップシューティングゲーム 手順19
 ゲーム終了/未終了の状態を設定値として用意し、拠点の耐久力が残っている場合は、ゲーム未終了(プレイ中)条件(耐久力が 0 以下)を満たした際にはゲーム終了と判定する処理を実装します。


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


 以下の内容で順番に実装を進めていきます。

手順19 −ゲーム終了判定の実装−
39.GameManager スクリプトを作成し、ゲーム終了/未終了の判定値を用意する
40.DefenseBase スクリプトを修正し、耐久力が 0 になった際にゲーム終了の判定を追加する



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

 ・以前の学習内容の復習



39.GameManager スクリプトを作成し、ゲーム終了/未終了の判定値を用意する

1.設計


 ゲーム終了/未終了、という単語をプログラム化していく手順を考えて設計を行います。
これはゲーム内の状態を表す言葉であり、この状態をプログラムとして管理することが出来れば、
「ゲーム未終了(プレイ中)の状態なら」、「ゲーム終了の状態になったら」という制御も行えるようになります。



 ゲーム終了/未終了状態になったら、という問いかけをプログラムで制御を行うには、「ゲーム終了/未終了」という情報を作るところから始めます。
この情報はプレイヤーやエネミーといった場所ではなく、ゲーム全体を管理する場所で管理することが望ましいです。
以上のことから、ゲームの管理用の GameManager スクリプトを新しく用意して、このスクリプト内に「ゲーム終了/未終了」という情報を管理させるように考えます。
情報が作成されて管理運用することができれば、それを他のスクリプトでも参照することによって、この情報を活用することが可能になります。



 この「ゲーム終了/未終了」の情報を、ゲーム内のすべてのスクリプトに利用する情報とすることで、
他のスクリプトは、GameManager スクリプトにある情報を参照し、確認すればいつでも、
現在のゲームの状態が「ゲーム終了の状態なのか」「ゲーム終了の状態ではないのか」がわかる設計とします。
こうしておくことで、''色々なスクリプト内に「ゲーム終了/未終了」を個別で管理する情報を作る必要はなくなり、
情報を一元化でき、把握を容易にする''ことができます。



 こういった状態の管理には、bool 型(あるいは enum 型)の変数を用意し、管理運用する手法が一般的です。
bool 型は真偽値(true、 あるいは false )としていずれかの情報のみを持ち、他の情報は値として代入されることはありません。
つまり、常にどちらから情報を値として管理している変数になります。

 この真偽値を、自分でどのように運用するかを設定し、true と false の値に役割を付与します

 今回の場合であれば「ゲーム終了/未終了」という状態を管理したいので、bool 型の変数を1つ用意します。
真偽値の役割は自由に設定できますが、true であればゲーム終了している状態false であればゲーム終了していない(未終了)状態、というように
自分で運用する際の役割を考えて、それに基づいて、ロジックを設計全体を考えていくようにします。これが定まらないとロジックが組めません

 新しく宣言した変数の情報を、プログラム内で利用し、設計上必要なタイミングで真偽値を切り替えるようにすれば運用することで可能です。



 今回のゲームでは、「ゲーム終了/未終了」が切り替わる状態としては、「拠点の耐久力の値が 0 以下になったとき」がまず、条件の1つとして考えられます。
そのため、DefenseBase スクリプトの耐久力の値が 0 以下になったタイミングで、この ゲーム状態管理用の変数を true に切り替える制御を行うことが出来れば
「耐久度が 0 以下になった」 => 「ゲーム終了の状態にする(true)」という形で処理が連動するようにロジックを組むことができます。

 そしてゲーム状態管理用の変数を参照することで、''「ゲーム終了の状態になったら」「ゲーム未終了の状態だったら」という条件を、
プログラム内で活用することが出来る''ようになります。


 日本語をプログラム化するときには、ロジックを組む、という言葉を使います。
物事を論理的に考えて、どのような制御の組みあわせを作ることによって、実装したい処理を実現できるように考えていきましょう。


2.GameManager ゲームオブジェクトを作成する


 このゲームオブジェクトはゲーム全体の管理を行う GameManager スクリプトをアタッチするための役割を持つものです。
スクリプトによってゲームの管理者としての振る舞いを与えられるようになりますが、プレイヤーやエネミーのようなゲーム画面に映すような情報ではありません。

 そのため、この GameManager スクリプト用のゲームオブジェクトは Canvas 内に設置する必要がありません。
ヒエラルキーに存在していれば問題ないためです。

 ヒエラルキーの空いている場所で右クリックをするか、ヒエラルキーの下にあるプラスボタンを押してメニューを開きます。
Create Empty を選択し、空のゲームオブジェクトを1つ作成します。名前を GameManager に変更してください。

 Transform コンポーネントの Position を確認し、0 以外になっていたら、Reset を実行して、すべての Position の値を 0 に戻してください。
(Canvas 外に作成しているので、RectTransform コンポーネントではなく、通常の Transform コンポーネントを持っています。)


ヒエラルキー画像



GameManager ゲームオブジェクト インスペクター画像



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


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


 設計に基づいて、宣言フィールドにゲーム状態管理用の変数として、 bool 型の isGameUp 変数を作成します。
他のスクリプトからもこの情報を参照したいため、public 修飾子で宣言をしておきます。
private 変数のままですと、他のスクリプトからこの情報にアクセスして利用することが出来ません。

 また、この isGameUp 変数の状態(真偽値)を変更するためのメソッドを準備しておいてください。
こちらのメソッドは、引数として bool 型の情報を受け取り、その値を isGameUp 変数に代入するように設定しておくと使いやすく便利です。

 処理のイメージが出来たら、自分で考えた場所に日本語のコメントを記述し、処理を書いていきましょう。


GameManager.cs

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


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


4.GameManager スクリプトを GameManager ゲームオブジェクトにアタッチし、設定を行う


 作成した GameManager スクリプトを、ヒエラルキーにある GameManager ゲームオブジェクトにドラッグアンドドロップしてアタッチします。
アタッチしたら必ず対象のゲームオブジェクトのインスペクターを確認して、スクリプトがアタッチされていることを確認します。


GameManager ゲームオブジェクト インスペクター画像



 以上でこのゲームオブジェクトは完了です。
isGameUp 変数が public 修飾子で宣言されているのでインスペクターに表示されていることが分かります。


40.DefenseBase スクリプトを修正し、耐久力が 0 になった際にゲーム終了の状態に切り替える判定を追加する

1.設計


 GameManager スクリプトにゲーム終了/未終了と判定をするための変数と、その変数を切り替えるためのメソッドが追加されました。
この変数は現在の所、ゲーム未終了の状態(false)にしている処理はありますが、ゲーム終了の状態となるための true に書き換える処理自体はまだありません。
メソッドは用意されていますが、このメソッドが引数に true の情報が渡されて実行されている処理が、まだないためです。

 つまり、この true に切り替える処理をどの部分に書くかによって、ゲーム/未終了の状態をゲーム終了の状態に切り替えられることになります。
そのため、しっかりとした設計に基づき、適切なタイミングで isGameUp 変数を true に切り替えるメソッドの呼び出し命令を記述しましょう。



 さて、どの部分にこの isGameUp 変数の値を false から true に切り替える処理を書いたらよいでしょうか。

 「ゲーム未終了の状態」とはすなわち、ゲームをプレイしている状態ですので、特定の状態になるまでは、このゲーム未終了の状態で問題ありません。
もしもゲーム内のプログラムで「ゲーム未終了の状態であるなら」という制御文が必要ならば、作成して運用することが可能です。

 「ゲーム終了の状態」ということは、通常であれば、ゲームをクリアしたときか、あるいはゲームオーバーになったとき、このいずれかが考えられます。
つまり、その状態になるタイミングに合わせて、この変数を true に切り替えることが出来れば正常な切り替えの管理が行えることになります。

 DefenseBase スクリプトの UpdateDurability メソッド内には TODO を記述してある場所があります。
この部分が、今回の設計対象となる部分です。このように TODO 機能を利用すると、先々の設計が楽になります。
「こういう処理を実装したい」というイメージが出来たときには、忘れずに TODO として記述したい処理のコメントを残しておきましょう。

 さて、この TODO の記述の1つに、「耐久力が 0 以下になった場合の処理を記述する」とコメントのある場所があります。
まさにこの部分が、今回の isGameUp 変数を切り替えるタイミングにピッタリです。
ここで変数を true に切り替えることによって、ゲームオーバーの状態を判断する値として、isGameUp 変数を利用することが可能になります。
 

2.DefenseBase スクリプトを修正し、GameManager スクリプトの情報を取得するメソッドを用意する


 設計と TODO のコメントを参考にしながら、isGameUp 変数の切り替え処理を記述してみてください。

 ただし、この isGameUp 変数を切り替えるメソッドは、DefenseBase スクリプトではなく、GameManager スクリプトに用意されている情報です。
そのためにまず、DefenseBase スクリプトに、GameManager スクリプトの情報を代入する変数と、取得して代入する処理が必要になります。

 取得の方法には、以前学習した、Start メソッドを public 修飾子に変更して呼び出すようにする処理を利用しましょう。
最大の利点は、引数が利用できることです。public 修飾子に変更したメソッドを GameManager スクリプトより呼び出すようにすれば、
引数として、DefenseBase スクリプトに必要な GameManager スクリプトの情報を送り届けてもらうことが出来ます。
つまり、ヒエラルキーにあるゲームオブジェクトを探して、GetComponent メソッドを利用しなくても、GameManager スクリプトを取得して変数に代入することが可能になります。
 
 それから注意点が1つあります。耐久力が 0 以下になったら、という条件のみの分岐の場合、
耐久力が 0 以下の際にエネミーが侵入するたびに耐久力が 0 以下として再度評価されるため、
その都度、分岐の条件を満たすことになり重複して処理が発生することになります。

 このような場合には、耐久力が 0 以下になったとき、さらに、isGameUp 変数が false ならと分岐の条件を強くすることによって制御する方法を検討してください。
同じ内容の処理は複数回処理をしない、という制御になるように設計を考えます。


DefenseBase.cs

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


 スクリプトを作成したらセーブします。
DefenseBase ゲームオブジェクトのインスペクターを確認します。
新しく宣言した変数は private ですので表示の追加はありませんので、アサイン済の情報が抜けてしまっていないかを確認しておきます。


DefenseBase ゲームオブジェクト インスペクター画像




3.GameManager スクリプトを修正する


 isGameUp 変数を切り替えるメソッドが作成済ですので、DefenseBase スクリプトが適切なタイミングで呼び出し命令をしてくれれば問題ありません。

 先ほど、DefenseBase スクリプトの Start メソッドがなくなって、代わりに public 修飾子のメソッドが完成しています。
このメソッドを呼び出さないと、GameManager スクリプトの情報が DefenseBase スクリプトに届かず、また、今まで自動で処理されていた Start メソッドの役割を果たせなくなります

 そのため、GameManager スクリプトに DefenseBase スクリプトの情報を代入する変数を用意して、該当の public 修飾子のメソッドを呼び出す処理を追記します。
呼び出すタイミングは、Start メソッド内がよいでしょう。なぜなら、新しく DefenseBase に作り直したメソッドの処理自体が以前は Start メソッド内で処理されていたものだからです。
同じタイミングで実行することができれば、メソッド自体は Start メソッドではありませんが、同じように処理をすることが可能です。
呼び出す際には、GameManager スクリプト自身の情報を引数として渡すようにしてください。こうすることによって、DefenseBase スクリプトに GameManager の情報を送ることができます。

 DefenseBase スクリプトを持つゲームオブジェクトは常にヒエラルキーにあるゲームオブジェクトですので、SerializeField 属性をつけて DefenseBase 型の変数を宣言すれば
インスペクターよりドラッグアンドドロップすることで、DefenseBase 用の変数に情報を代入することが出来ます。

 それではまずは、自分で考えたロジックを書いてみてください。


GameManager.cs

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


 スクリプトを修正したらセーブします。GameManager ゲームオブジェクトのインスペクターを確認します。
新しく SerializeField 属性で宣言した変数が表示されていれば問題ありません。


GameManager ゲームオブジェクト インスペクター画像




4.GameManager ゲームオブジェクトの設定を行う


 GameManager ゲームオブジェクトを選択し、インスペクターに新しく表示されている defenseBase 変数に情報を登録します。
ヒエラルキーにある DefenseBaseSet ゲームオブジェクトをドラッグアンドドロップしてアサインします。
自動的に、DefenseBase スクリプトの情報が変数内に登録されます。


GameManager ゲームオブジェクト インスペクター画像



 以上で設定は完了です。


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


 設定とスクリプトの修正が終了しましたので、ゲームを実行して処理を確認していきます。

 ゲーム開始時は、isGameUp 変数は false であることを確認します。
正しく制御されているならば、この変数は耐久力が 0 以下になるまでは true になることはありません。

 エネミーが拠点に侵入し、耐久力が 0 以下になった際には、今回実装した処理が実行されます。
Console ビューに GameOver と表示されて、GameManager スクリプトの isGameUp 変数が true になってチェックが入れば制御成功です。

 その後、別のエネミーが拠点に侵入しても、この GameOver の処理は1回しか表示されていないことも一緒に確認します。
もしも何回も表示されてしまう場合には、耐久力が 0 以下の部分の分岐に何回も入ってしまっていることになりますので、制御を見直す必要があります。


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


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


 次は 手順20 −ゲーム終了判定の値を利用した制御処理の実装− です。