Skip to content
第02回 プレイヤーの弾とオブジェクトプール

3 / 4 射撃入力と発射間隔

このステップの目的

射撃入力をゲーム内のアクションへ追加し、プレイヤーが弾を発射できるようにする

物理キー入力をゲームが求めるアクションに変換する

このシューティングゲームは、物理キーの入力を直接扱わずにInputクラスで定義されたActionを通してゲーム内のアクションとして扱います。
今回はZキーとSpaceキーをShootアクションに割り当て、双方のキー入力でプレイヤーが弾を発射できるようにします。

以下の通り、Input::ActionShootを追加し、Input.cppの対応表にキーコードを追加してください。

Input.hcpp
    class Input
    {
    public:
        // ゲーム内で扱う入力別のアクション
        enum class Action
        {
            // 省略
            MoveLeft,
            MoveRight,
            MoveUp,
            MoveDown,
            Shoot, // ← 追加するアクション
            Exit
        };
Input.cppcpp
    // ゲーム内の操作と物理キーの対応表
    constexpr Binding Bindings[] =
    {
        // 省略
        { Action::MoveDown, KEY_INPUT_DOWN },
        { Action::MoveDown, KEY_INPUT_S },
        { Action::Shoot, KEY_INPUT_Z },     // ← 追加するキー
        { Action::Shoot, KEY_INPUT_SPACE }, // ← 追加するキー
        { Action::Exit, KEY_INPUT_ESCAPE }
    };

発射間隔を設定する

今回のゲームでは、プレイヤーの弾は発射ボタンを押し続けることで連射できるものとします。

ですが、ただ入力に合わせて毎フレーム弾を発射するのはあまりゲーム的ではありません。
間断なく弾が発射されると弾の動きを作る空白がなくなり、止め絵の表示と変わらなくなってしまいます。
ゲームは動きのメリハリが重要です。

ここでは入力判定にあわせて、発射間隔を設けることにします。

まず、プレイヤーの発射処理を扱う関数Player::TryShoot()を用意します。
TryShoot()Shootアクションの入力と発射間隔を判定し、発射の際はPlayerShotManagerに弾の生成を依頼します。

入力があっても発射間隔のタイマーの値が0よりも大きい場合は、その前の射撃からまだ十分な時間が経過しておらず、発射できない状態です。
弾を生成できた場合だけ、発射間隔のタイマーを発射間隔の値にリセットします。

Player.hcpp
    // 入力と発射間隔を確認し、弾を生成できたらtrueを返す
    bool TryShoot(const Input& input, PlayerShotManager& shots);

    private:
        // 発射する弾の中心座標を返す
        Vector2 GetMuzzlePosition() const;

        // 次の発射までの残り時間 単位は秒
        float shotTimer = 0.0f;

発射間隔の値は、ほかの調整値と同じくGameTuning.hに定義します。 発射間隔の値が小さいほど連射速度は速く、大きいほど遅くなります。

GameTuning.hcpp
namespace player
{
    inline constexpr float ShotInterval = 8.0f * config::DeltaTime;
}

TryShoot()は、射撃入力、タイマー、弾の生成リクエストを一つの発射処理として扱いますが、弾の生成自体はPlayerShotManager::Spawn()が担当します。
PlayerShotManager::Spawn()が成功した場合は全ての条件を満たしたものとして発射間隔のタイマーをリセットします。

Player.cppcpp
using Action = Input::Action;

bool Player::TryShoot(const Input& input, PlayerShotManager& shots)
{
    if (!input.IsDown(Action::Shoot) || shotTimer > 0.0f)
        return false;

    if (!shots.Spawn(GetMuzzlePosition()))
        return false;

    shotTimer = tuning::player::ShotInterval;
    return true;
}

タイマーは毎フレーム少しずつ減らし、0でクランプします。 タイマーの値が0であれば、次の弾を発射できる状態です。

Player.cppのUpdateへ追加cpp
shotTimer = std::max(0.0f, shotTimer - deltaTime);

発射位置を決める

通常、プレイヤーの発射する弾の位置はプレイヤーの位置に依存します。
よって、発射位置を求める処理はPlayerの責務とするのが自然です。

ここでは発射位置を求める関数を設け、TryShoot()の内部から利用するものとします。
発射位置はゲームの外部から直接使う情報ではないため、privateメンバーとしてGameから呼び出せないようにします。

今回、弾の発射位置の中心はプレイヤーの命中範囲の上端に合わせています。
ゲームの仕様に合わせて(好みで)どの位置にしても構いませんので、変更調整してみてください。

Player.cppcpp
private:
    Vector2 Player::GetMuzzlePosition() const
    {
        const Rect& shotHitbox = tuning::playerShot::Hitbox;
        Rect hitbox = GetHitbox();

        return
        {
            hitbox.GetCenter().x,
            hitbox.y - shotHitbox.height * 0.5f
        };
    }
チェックリスト
  • ZキーとSpaceキーがAction::Shootへ変換されている
  • 発射処理をPlayer::TryShoot()へまとめている
  • PlayerShotManagerの生成に成功したときだけ発射間隔を開始している
  • 発射位置がプレイヤーの上端にある