読者です 読者をやめる 読者になる 読者になる

夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【偽スペースハリアー】超能力ホーミングミサイル発射!

cocos2d スペースハリアー TravelShooting JP

今までは敵キャラを避けるだけだったハリアー君ですが、今回ようやく攻撃能力を身につけました。

本物のスペースハリアーと同様に、弾はロックオンした敵に向かって飛んで行きますので、敵をやっつけるのは比較的簡単です。
弾は一定間隔で自動的に発射されますが、ロックオンしていないときの処理をまだ実装していないので現時点では無駄弾は発射されません。

まずは、動画を見ていただきたいと思います。爆発は音だけで表現しているので少しもの足りないと思います。

「ピンピンピンピン」という短い音がロックオンの音です。ハリアーの前方に敵がいるとロックオンします。この「前方」という判定は3D座標ではなく、画面上の座標で行っています。
なので、3D的な位置関係を気にする事無く敵と画面上の座標を合わせるようにするとロックオンできます。多分本物もそうなっていると思います。

毎フレーム全部の敵とハリアーの座標を比較し、ロックオン範囲内にいる敵をNSMutableArrayに入れておきます。
弾を撃つときはそのアレイから敵オブジェクトの参照を取り出し、弾のターゲットとして使用します。

弾の移動処理ではターゲットの座標から自分の座標を引いたベクトルの方向に弾の速度分移動します。
このときターゲットの座標が移動速度分の距離より近ければターゲットに当ったと判定しています。

3Dのベクトル演算は2Dの計算を2回行って求めています。(もっと効率の良い方法があるかもしれませんが)

例え3D座標のベクトルの長さは次のように計算しています。

typedef struct {
    CGPoint position;
    CGFloat z;
} Position3D;

- (CGFloat)lengthPosition3d:(Position3D)pos3D
{
    return ccpLength(ccp(ccpLength(pos3D.position), pos3D.z));
}

Position3D構造体は3Dの座標を入れておくための物で、CGPoint positionにx, y座標、CGFloat zにz座標を入れます。
ccpLength(pos3D.position)でまずxy平面でのベクトルの長さを求め、そのベクトルとz座標からなる平面でさらにベクトルの長さを求めることで3D空間でのベクトルの長さとしています。

ベクトルのノーマライズも同様のメソッドを作成して計算します。
足し算、引き算、かけ算はxy成分、z成分に分けて普通に計算できます。

追記:0での割り算を防ぐためのメモ
xy成分とz成分に分けたとき、xyのベクトルが0になると計算上問題になる場合があります。
ノーマライズの計算では割り算が出てきますので、このとき0で割らないための注意が必要になります。下の3Dベクトルのノーマライズメソッドではxy成分が0のときは割り算をスキップしています。
z成分があるので計算上xy成分はccp(0, 0)で問題ありません。
xyzが0の時は計算自体があり得ないので本来は呼び出し側でチェックすべきところです。
このメソッドではデバッグ用にNSAssert3でクラッシュさせています。
毎回エラーチェックの為だけにlengthXYZを計算していますのでパフォーマンス的には無駄になってしまっています。

// Position3Dのベクトルをノーマライズ
- (Position3D)normalizePosition3D:(Position3D)pos3D
{
    CGFloat lengthXY = ccpLength(pos3D.position);
    CGPoint vectorXYZ = ccp(lengthXY, pos3D.z); // x-yの斜辺をx, zをyとしたCGPoint
    CGFloat lengthXYZ = ccpLength(vectorXYZ);
    NSAssert3(lengthXYZ != 0.0, @"Divided by zero x = %f, y = %f, z = %f", pos3D.position.x, pos3D.position.y, pos3D.z);
    CGPoint normal3D = ccpNormalize(vectorXYZ); // zはこの時点でノーマライズされている
    CGPoint normalXY;
    if (lengthXY == 0.0) {
        normalXY = ccp(0, 0); // 0での割り算はできないので
    } else {
        CGFloat ratio = normal3D.x/lengthXY; // 元のX,yの斜辺とノーマライズしたx,yの斜辺の比率
        normalXY = ccpMult(pos3D.position, ratio); // x,yをノーマライズ
    }
    Position3D returnPos;
    returnPos.position = normalXY;
    returnPos.z = normal3D.y;
    return returnPos;
}

さらに追記:
ちょっと3Dベクトルの計算について勉強しました。
x, y, z成分をばらして計算した方がばらす手間よりはるかに計算量が少なくなりそうなことがわかりましたので、ベクトルの長さの計算、ノーマライズの計算を次のように改良しました。
ずいぶんすっきりしました。
エラーチェック用の無駄な演算も必要ありません。

- (CGFloat)lengthPosition3d:(Position3D)pos3D
{
    CGFloat x = pos3D.position.x;
    CGFloat y = pos3D.position.y;
    CGFloat z = pos3D.z;
    CGFloat lengthXYZ = sqrtf(x * x + y * y + z * z);
    return lengthXYZ;
}

- (Position3D)normalizePosition3D:(Position3D)pos3D
{
    CGFloat lengthXYZ = [self lengthPosition3d:pos3D];
    NSAssert3(lengthXYZ != 0.0, @"Divided by zero x = %f, y = %f, z = %f", pos3D.position.x, pos3D.position.y, pos3D.z);
    pos3D.position.x /= lengthXYZ;
    pos3D.position.y /= lengthXYZ;
    pos3D.z /= lengthXYZ;
    return pos3D;
}