長い間に渡って解説を続けてきたタワーディフェンスゲームの作り方だが、今回でひとまずの完結となる。最後は、敵を砲弾で攻撃して倒す処理を追加しよう。また、次々と敵が攻め上がってくる様も実装する。タワーディフェンスの真髄となる部分はできあがったと言えるだろう。
敵の体力と複数の敵の管理
ここまでは、敵は単に道を歩いてくるだけだった。さらに踏み込んで、その敵を攻撃して倒せるようにしてみよう。そのためには、敵に体力を設定する必要がある。また、タワーディンフェンスゲームの特徴は、複数の敵が次々と波状的に攻め上がってくるところである。この管理も行なおう。
敵の情報は、ES1Rendererクラスのインスタンス変数で管理してきた。敵の座標を表すenemyX、enemyYや、現在の画像を表すenemyImageなどである。これらに、敵の体力を表すものも追加しよう。enemyHitPointという名前にする。
さらに、複数の敵を管理できるようにする。もっとも簡単な方法は、敵情法を管理するための変数を配列にしてしまうことだ。この場合、現在何匹の敵がいるかを記憶しておく必要も出てくる。そこで、enemyNumberという変数も追加する。
これらの変更を行うと、クラスの定義は次のようになる。
List 1.
@interface ES1Renderer : NSObject <ESRenderer>
{
...
// 敵の座標
GLfloat enemyX[8];
GLfloat enemyY[8];
GLint enemyImage[8];
GLuint enemyStep[8];
GLint enemyRoadIndex[8];
GLint enemyHitPoint[8];
GLint enemyNumber;
...
敵に関する変数を、すべて配列に変更した。これにより、いままでのソースコードも修正して、添字をつけてアクセスすることになる。配列の大きさである8は、同時に出現可能な敵の数を表すことになる。敵の情報は、配列の0番目から順々に入れていくことにしよう。敵の数は、enemyNumberで管理する。
これで、複数の敵に対応できるようになった。
新たな敵の追加
複数の敵を管理できるようになったので、実際に登場させてみよう。ここでは、一定時間間隔で新たな敵が現れるものとする。
「一定時間間隔」を得るためには、ゲーム全体で時間を管理する必要がある。そのために、stepという名前のインスタンス変数を追加した。これは、ゲーム内部で1ステップ進む毎に1増加し、全体で何ステップ経過したかを記録しておくためのものである。
では、新たな敵の追加を行ってみよう。敵の追加を行なう、addNewEntryメソッドを紹介する。このメソッドは、renderメソッドから呼び出す。
List 2.
- (void)addNewEnemy
{
// ステップ数をチェックする
if (step % 256 != 0) {
return;
}
// 敵の数をチェックする
if (enemyNumber >= 8) {
return;
}
// 敵情報の初期値を設定する
enemyX[enemyNumber] = roadPoints[0];
enemyY[enemyNumber] = roadPoints[1];
enemyImage[enemyNumber] = 0;
enemyStep[enemyNumber] = 0;
enemyRoadIndex[enemyNumber] = 0;
enemyHitPoint[enemyNumber] = (step / 256 + 1) * 10;
// 敵の数を増やす
enemyNumber++;
}
まず、ステップ数をチェックする。ここでは、256ステップ毎に新たな敵が登場することにしてみた。同時に、現在の敵の数が最大数に達していないかどうかをチェックする。
それらのチェックを通過したら、新たな敵を追加しよう。敵情報の配列に、初期値を設定する。注目してほしいのは、体力を表すenemyHitPointの値だ。これは、step数を256で割ったものに10をかけている。つまり、新たな敵が登場する毎に、そのヒットポイントが10ずつ増えていくのだ。
最後に敵の数を表すenemyNumberの値を増やして、敵の追加処理は終了だ。
敵への攻撃と敵を倒したときの処理
この敵に対して攻撃を行なおう。無事に倒したときは、その処理も必要だ。これらは、砲弾を描くdrawShellメソッドで行なうことにする。drawShellメソッドでは砲弾の座標の更新を行なっているので、その後で砲弾座標と敵座標の比較を行なえば良い。
次のようなソースコードを書いてみた。
List 3.
- (void)drawShell
{
...
// 敵に当たった場合
if (ABS(shellX[i] - enemyX[0]) < 0.05f &&
ABS(shellY[i] - enemyY[0]) < 0.05f)
{
// 砲弾の位置を無効化する
shellX[i] = 1000.0f;
shellY[i] = 1000.0f;
// 敵の体力を減らす
enemyHitPoint[0]--;
if (enemyHitPoint[0] <= 0) {
// 敵を一体減らす
enemyNumber--;
if (enemyNumber == 0) {
break;
}
int j;
for (j = 0; j < enemyNumber; j++) {
enemyX[j] = enemyX[j + 1];
enemyY[j] = enemyY[j + 1];
enemyImage[j] = enemyImage[j + 1];
enemyStep[j] = enemyStep[j + 1];
enemyRoadIndex[j] = enemyRoadIndex[j + 1];
enemyHitPoint[j] = enemyHitPoint[j + 1];
}
}
}
...
}
まず、砲弾が敵に当たったかどうかを判定する。これには、先頭の敵の座標を使うものとする。砲台は、常に先頭の敵を狙うことにしているためだ。
敵に当たった場合は、まず砲弾の座標を無効化することで、それを消す。さらに、敵の体力を減らす。ここで敵の体力が0以下になれば、この敵が倒れたということになる。
敵が倒れた場合、敵の数を一体減らす。そして、インスタンス変数にある敵情報の配列にある値を、1個ずつ左にずらしてやる。これは、敵情報の値は配列の先頭から0、1、2、と入っており、常に先頭である0番目の敵が倒されるので、倒れた後は繰り上げる必要があるためだ。
これが、敵への攻撃と倒す処理になる。
完成したタワーディフェンス
これで、タワーディフェンスものらしい動きができるようになった。攻め上がってくる敵を撃退するが、次々と現れる敵にジリジリと押されていく、タワーディフェンスゲームの真髄となる部分が表現されている。
あとは、様々なファクターを追加していくことで、よりゲームらしくなるだろう。
ここまでのソースコード: Defense-4.zip