キャラクターを動かしたり、ぶつかって爆発させるだけでは、通常はゲームになりません。タイトルからゲームを開始したり、点数を加えたり、ゲームオーバーにしたり、クリアしたりと、ゲームを進行させていく仕組みが必要です。本章では、キャラとゲームシステムを連携させるための仕様を解説します。
実装にとりかかる前に、考えられることを可能な限り、具体的に書き出します。書き出したものを、仕様書と呼びます。
プレイヤーやマップのキャラをはじめとする、ゲームを構成しているオブジェクトを挙げます。企画概要書で描いたサムネイル(図4.1)や、ゲーム画面を想定して作成した見本画像であるモックアップ(図4.2、図4.3)が参考になります。
図4.1: ゲーム画面のサムネイル
図4.2: Stage1のモックアップ
図4.3: Stage2のモックアップ
画面イメージを眺めながら、目についたものをオブジェクトとして書き出します。
これらが、表示されているオブジェクトです。
目に見えないオブジェクトもあります。それらを見つけるために、ゲームがはじまってからの進み方を書き出します。
ここからは、ゲーム中に起きたことに基づいて、ゲームが進行します。
ゲームの進み方が明らかになりました。これらを管理するためのオブジェクトを挙げます。
以上で、ゲームを構成するオブジェクトの概要が把握できました。これらのオブジェクトがやりとりして、ゲームを成立させます。
本書で扱うのは、プレイヤーや爆弾などのキャラの実装です。そこに関連するオブジェクトの詳細を検討して、解説します。
プレイヤー、爆弾、コイン、木箱は、次のような動作が必要です。
これらを管理するクラスと、プレイヤーなどのすべてのキャラに、進行を通知するオブジェクトに伝える機能のクラスを用意します。
ゲームオーバーは、爆弾に触れたときに、プレイヤーや爆弾から、ゲームオーバーの要求を受け取ります。また、クリアは、コインを取ったときに、コインの数を管理するクラスで判定されて、このクラスに要求が出されます。同一フレームで、ゲームオーバーとクリアが同時に発生したときは、クリアを優先することにします。これは、プレイヤーと爆弾が接触した時点では、まだゲームオーバーになることは確定していないことを表します。クリアが報告されなかったときに、ゲームオーバーになることが確定して、キャラに知らされます。
通知を必要とするオブジェクトに、一斉に通知する方法は、いくつか考えられます。簡単なのは、ゲームの進行管理クラスをstaticにすることです。システムのどこからでもアクセスできるので、通知が必要なオブジェクトからポーリングします。
また、通知を送信するオブジェクトが、通知を受け取るためのインターフェースを検索しておいて、通知したいときに、インターフェースの通知メソッドを呼び出す方法が考えられます。
staticを使う方法は、手軽な反面、テスト時に機能が入れ替えにくいので、嫌われる傾向にあります。staticでの実装は簡単なので、今回は後者で実装します。
通知を受け取りたいクラスに、IGameStateListenerインターフェースを実装します。ステージが開始する時に、システムがIGameStateListenerを実装しているクラスを検索して、進行に応じて、メソッドを呼び出します。各ゲームオブジェクトは、インターフェースで定義されているメソッドに、進行に応じて状態を変える処理を実装します。
爆弾やコインは、ステージからなくなる可能性があります。リストやイベントに、消えたオブジェクトのインスタンスが残っていると、実行時にエラーがでてしまいます。そこで、状態の切り替えに加えて、消えた時に、自身のインスタンスを渡して、リストから削除を要求する機能も、IGameStateListenerに定義します。
IGameStateListenerの定義は、リスト4.1の通りです。
リスト4.1: IGameStateListenerインターフェース
public interface IGameStateListener {
void OnReset();
void OnGameStart();
void OnGameOver();
void OnClear();
UnityEvent<IGameStateListener> GameStateListenerDestroyed {get;}
}
ゲームオーバーを要求する機能を持たせたいクラスに、IGameOverEmitterインターフェースを実装することにします。ゲームシステムは、シーンからこのインターフェースを実装しているオブジェクトを検索して、ゲームオーバーを要求するメソッドを登録します。ゲームオーバーを要求するときは、受け取ったメソッドを呼び出します。
実行者から直接メソッドを呼び出すのではなく、実行してもらいたい側から実行者にメソッドを渡して、必要なタイミングで呼び出してもらう方法を、オブザーバーパターンと呼びます。Unityのボタンがこれで、UnityEventやUnityActionを使うと、手軽に実装できます。
このインターフェースを実装するのは、プレイヤーか爆弾のどちらでも構いません。実装時に、検討します。
クリアの要求は、ゲームオーバーよりやや複雑です。
コインを取ったら、コインの数を管理するオブジェクトに知らせて、残り数を減らします。残りが0になったら、クリアを要求します。コインの残り数を管理するクラスは、値オブジェクトとして実装します。それを、どこに持たせるかを検討します。
コインを取ったことは、プレイヤーとコインの双方が知り得ます。ただ、双方とも、コインの数を管理する役目ではないと感じます。ゲームのパラメータなので、ゲームの進行管理クラスにもたせるのが自然です。
以上から、クリアを要求するのではなく、プレイヤーかコインから、ゲームの進行管理オブジェクトに、コインを取ったことを知らせることにします。クリアするかどうかは、ゲームの進行管理側で判断します。
ここまで整理したら、ゲームオーバーと同様です。コインを取ったことを通知するIGetCoinEmitterを用意して、プレイヤーかコインに実装します。ゲームの開始時に、このインターフェースをもつインスタンスを検索して、コインを取ったときに呼び出して欲しいメソッドを渡します。これも、オブザーバーパターンです。
スコアは、ゲームシステムのデータとして、定義しています。ゲームシステムのデータは、ゲームの進行管理クラスからアクセスできます。
スコアは、コインを取ったときに入ります。コインを取ったら、プレイヤーかコインから、ゲームの進行管理オブジェクトに基本点が報告されます。そこで、残り時間を反映させて、点数を加えればよさそうです。
企画の構想をもとにして、目に見えるオブジェクトや、ゲームの進行に必要なオブジェクトを列挙しました。それらがどのように連携するかを検討しました。
企画構想で、面白い場面の具体例や、プロットを書き出したことが、ここで活きてきます。何もない状態で、このような作業をするのは大変です。考えたことは、すぐに具体的に書き出します。書き出したものを目で見ることで、想像が補強されて、より具体的なイメージが沸いてきます。それを書き出していくことを繰り返すことで、頭の中のゲームを、現実に変換します。
オブジェクトや進行が多すぎて、すべて挙げるのが難しいと感じたら、企画を分解して、小さくしましょう。考えきれないものを完成させることは困難です。確実に完成できる規模の企画を作って、ゲームを完成させる経験を積むことが大切です。