第3回:プレイヤーから弾を発射する

 プレイヤーから敵を破壊するための弾を発射するには、発射ボタン(ここではスペースキー、あるいはゲームパッドの1ボタン)が押されたときに、プレイヤーがいる場所を初期位置として弾クラスをタスクに追加するとよいでしょう。

 CCharactorの派生となるCPlayerBulletクラスを作成します。このクラスはCPlayerクラスが記述されているソースコードファイル内に併記しても問題ありません。クラス作成時に渡す初期パラメータは発射位置の座標だけでも事足りるのですが、今回は3方向に弾が発射される3way弾も実装しようと思いますので、発射角度もパラメータとして用意しています。

 プレイヤーの弾はゲーム中に数百発単位で発射されるため、CPlayerBulletが作成されるたびにFindObjectで弾のテクスチャクラスを検索するのは効率が悪いため、アイテムボックス(ハッシュクラス)に格納したものを呼び出すようにしましょう。
#define BULLET_PRIORITY 55000

class CPlayerBullet : public CCharactor
{
public:
    CPlayerBullet(float sx, float sy, float moveangle);
private:
    float angle;
protected:
    virtual void Init();
    virtual void Exec();
};
 CPlayerBullet::Exec()で弾の移動処理を行います。弾のy座標が-16未満、つまり、画面の上より外に出たら自分自身を削除して、弾の処理を終わらせています。
CPlayerBullet::CPlayerBullet(float sx, float sy, float moveangle)
{
    x = sx;
    y = sy;
    angle = moveangle;
}

void CPlayerBullet::Init()
{
    sprite.SetTexture((CTexture*)FindItemBox("pbullet"));
}

void CPlayerBullet::Exec()
{
    const float move = 8.0f;
    float mx = move * sinf(angle);
    float my = move * cosf(angle);

    x += mx;
    y -= my;

    if(y < -16.0f){
        RemoveObject(this);
        return;
    }

    sprite.Draw(x, y, angle);
}
 それではCPlayerクラスに弾の発射処理を追加しましょう。単純に考えれば、CInput::IsButtonPressed()がtrueを返せば、弾を発射するというルーチンを組めばよいことになります。しかしながら、これでは連射の速度はプレイヤーの能力に左右されるため、レーザーのように発射できる人がいる一方、2,3秒に1発のペースでしか発射できない人がいるので、ゲームバランスが崩れますし、なによりも指が疲れてしまいます。

 この問題を解決するため、ボタンを押しっぱなしでオート連射が行えるようにプログラミングしましょう。以下のプログラムコードでは、ボタンが押しっぱなしの場合は、数フレーム毎に1回ボタン入力を受け付けることにし、ボタン連打による連射時はボタン入力を受け付けるフレーム間隔を短くすることで、連打による連射のメリットが享受できるようにしています。
class CPlayer : public CCharactor
{
    /* 省略 */

private:
    int shoottime;  // 最後に発射されてからの時間
    void Shoot();
};
void CPlayer::Init()
{
    /* 省略 */

    missile = *(CSound*)FindItemBox("missile");

    shoottime = 0;
}

void CPlayer::Exec()
{
    /* 省略 */

    // ↓追加するプログラムコード
    if(shoottime > 10 && (input->IsKeyDown(DIK_SPACE) || input->IsButtonDown(0)))
    {
        // 最後の発射から10フレーム経っていれば
        // オート連射の入力を受け付ける
        Shoot();
        shoottime = 0;
    }
    else if(shoottime > 5 && (input->IsKeyPressed(DIK_SPACE) || input->IsButtonPressed(0)))
    {
        // 最後の発射から5フレーム経っていれば
        // マニュアル連射の入力を受け付ける
        Shoot();
        shoottime = 0;
    }else{
        shoottime++;
    }


    sprite.Draw(x, y);
}

void CPlayer::Shoot()
{
    AppendObject(new CPlayerBullet(x, y - 8.0f, d2r(0.0f)), BULLET_PRIORITY, true);
}
 wimmain.cppに弾のテクスチャクラスをタスクに追加する処理を加えれば、プレイヤーから弾を発射させる基本処理は完了です。
    game.AppendObject(new CPlayer(), PLAYER_PRIORITY, true);

    // ↓弾のテクスチャデータを追加
    CTexture *bullettex = new CTexture(_T("data\\pbullet.png"));
    game.AppendItemBox("pbullet", bullettex);
    game.AppendObject(bullettex, 1000, true);

    ShowWindow(mainWnd, nCmdShow);