前回は、敵を動かすところまで説明した。今回は、味方であるキャノンを配置しよう。

タッチイベントの取得

敵は道に沿って攻め上がってくるので、これをキャノンを使って迎え撃つのがタワーディフェンスゲームの基本だ。キャノンをうまく配置することがゲームの中心になる。キャノンの配置位置は、やはりiPhoneらしく、画面をタッチすることで決定するべきだろう。そこで、OpenGLを使ったアプリでのタッチイベントの取り扱い方を説明しよう。

iPhoneアプリでは、タッチイベントはUIViewクラスで受け取る。touchesBegan:withEvent:、touchesMoved:withEvent:、touchesEnded:withEvent:といったメソッドが用意されているので、これらを上書きすることでタッチイベントをトリガーとした処理を行なうことができる。今回のアプリではEAGLViewクラスがあるので、このクラスで受け取ることにしよう。

だが、実際にゲーム処理の中心となっているのは、Rendererクラスである。EAGLViewでタッチ関係の処理を行うのは都合が悪いので、タッチイベントはそのままRendererクラスに渡してしまうことにしよう。そのために、ESRendererプロトコルに次のようなメソッドを追加した。

List 1.

@protocol ESRenderer <NSObject>

...

@optional
- (void)view:(UIView*)view touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)view:(UIView*)view touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)view:(UIView*)view touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)view:(UIView*)view touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event;

@end

UIViewのtouchesBegan:withEvent:などから、そのままパススルーできるようにしてみた。実際には次のように使っている。

List 2.

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    // レンダラクラスにパススルーする
    [renderer view:self touchesBegan:touches withEvent:event];
}

これで、ESRendererクラスの方でタッチイベントを処理できるようになった。

座標系の変換

タッチイベントを受け取ると、そこからタッチした座標を取得することができる。これを使えば、キャノンを配置したりドラッグして移動したり、といった処理を行うことができる。

だが一つ問題がある。タッチイベントから取得した座標は、Cocoa座標系での値になっているのだ。今回のアプリはOpenGL座標系で描画行なっている。このままでは値を使うことができないので、変換処理を行なわなくてはいけない。

この変換は比較的簡単である。中心座標と倍率が違うだけなので、単純な線形変換で行なうことができる。次のようなソースコードになるだろう。

List 3.

    // タッチ座標を取得する
    CGPoint point;
    point = [[touches anyObject] locationInView:view];

    // 座標をCocoa座標系からOpenGL座標系に変換する
    float   x, y;
    x = (point.x - 160.0f) / 160.0f;
    y = (240.0f - point.y) / 160.0f;

まずタッチ座標を取得しているが、これにはUITouchクラスのlocationInView:メソッドを使う。引数のビューとしては、EAGLViewを指定しよう。

続いて座標の変換を行っている。320 x 480のCocoa座標系から、2.0 x 3.0のOpenGL座標系に変換している。160や240という値を足したり引いたりしているのは、原点の移動を行なうためだ。Cocoa座標系は画面左上が原点なのに対して、今回のOpenGL座標系は画面中央が原点となる。

これでタッチイベントと画面上のオブジェクトを関連づけることができるようになった。

キャノンの配置

では、キャノンを配置してみよう。

実際のゲームでは、キャノンを購入して画面上に配置していくことになる。だがここでは話を簡単にするため、あらかじめ3台のキャノンが配置されていることにしよう。これらをドラッグして好きな場所に移動させることにする。

インスタンス変数に、キャノンの座標のための変数を追加する。3台分だ。さらにドラッグして移動させるために、現在ドラッグ中のキャノンのインデックスを保持しておく変数も用意しておく。

List 4.

    // キャノンの座標
    GLfloat canonX[3];
    GLfloat canonY[3];
    int     draggingCanon;

この座標にキャノンを描くことになる。描画を行なうメソッドは、前回までに紹介した敵を描画するものとほぼ同じものになる。ここでは、キャノンをドラッグして動かすソースコードを説明しよう。まず、タッチ座標の下にキャノンがあるかどうかの判定を行なうメソッドを紹介する。

List 5.

- (int)touchedCanonWithView:(UIView*)view touches:(NSSet*)touches event:(UIEvent*)event
{
    // タッチ座標を取得する
    CGPoint point;
    point = [[touches anyObject] locationInView:view];

    // 座標をCocoa座標系からOpenGL座標系に変換する
    float   x, y;
    x = (point.x - 160.0f) / 160.0f;
    y = (240.0f - point.y) / 160.0f;

    // タッチ座標の下にあるキャノンを探す
    int i;
    for (i = 0; i < 3; i++) {
        if (x > canonX[i] - 0.1f && x < canonX[i] + 0.1f &&
            y > canonY[i] - 0.1f && y < canonY[i] + 0.1f)
        {
            return i;
        }
    }

    return -1;
}

まず、タッチ座標をOpenGL座標に変換する。その後、canonXとcanonYの変数と比較することでキャノンの検索を行っている。ここで気をつけたいのは、判定を行なうときにある程度の幅を持たせることだ。iPhoneはタッチで操作するデバイスだが、タッチによる操作ではピンポイントで座標を決定することは難しい。判定に余裕を持たせないと、なかなかキャノンが選択できないことになってしまう。

続いて、キャノンのドラッグを行なうメソッドを紹介しよう。

List 6.

- (void)dragCanonWithView:(UIView*)view touches:(NSSet*)touches event:(UIEvent*)event
{
    // ドラッグ中のキャノンを確認する
    if (draggingCanon == -1) {
        return;
    }

    // タッチ座標を取得する
    CGPoint point;
    point = [[touches anyObject] locationInView:view];

    // 座標をCocoa座標系からOpenGL座標系に変換する
    float   x, y;
    x = (point.x - 160.0f) / 160.0f;
    y = (240.0f - point.y) / 160.0f;

    // 座標を設定する
    canonX[draggingCanon] = x;
    canonY[draggingCanon] = y;
}

先ほどの決定したドラッグ中のキャノンに、タッチ座標を設定してやるのだ。

これらのメソッドを、タッチイベントを受け取るview:touchesBegan:withEvent:らの中から呼んでやればいい。これでキャノンのドラッグによる移動が実現できるのだ。

ここまでのソースコード: Defense-3.zip