長い間に渡って解説を続けてきたタワーディフェンスゲームの作り方だが、今回でひとまずの完結となる。最後は、敵を砲弾で攻撃して倒す処理を追加しよう。また、次々と敵が攻め上がってくる様も実装する。タワーディフェンスの真髄となる部分はできあがったと言えるだろう。

敵の体力と複数の敵の管理

ここまでは、敵は単に道を歩いてくるだけだった。さらに踏み込んで、その敵を攻撃して倒せるようにしてみよう。そのためには、敵に体力を設定する必要がある。また、タワーディンフェンスゲームの特徴は、複数の敵が次々と波状的に攻め上がってくるところである。この管理も行なおう。

敵の情報は、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