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

4 / 4 弾を更新して再利用する

このステップの目的

弾を毎フレーム更新して移動させ、画面外に出た弾をプールに戻して再利用する

ゲームループへ組み込む

弾はプレイヤーの後に更新します。

Game.cppcpp
void Game::Update(float deltaTime)
{
    player.Update(input, deltaTime);
    playerShots.Update(deltaTime);

    player.TryShoot(input, playerShots);
}

void Game::Draw() const
{
    player.Draw(images);
    playerShots.Draw(images);
}

画面外に出た弾を停止する

PlayerShotの更新はPlayerShotManagerが一括で行いますが、単にループを回すだけです。

PlayerShotManager.cppcpp
void PlayerShotManager::Update(float deltaTime)
{
    for (PlayerShot& shot : shots)
        shot.Update(deltaTime);
}

弾の寿命判定は個々の弾が行います。
ここでは弾の命中範囲全体が画面上端より上へ出たら消滅したものとします。

寿命が来た弾は自身でDeactivate()を呼び出します。 停止した弾は削除されず、非アクティブになるだけです。
非アクティブな弾は次の発射の際にPlayerShotManager::Spawn()が再利用します。

PlayerShot.cppcpp
void PlayerShot::Update(float deltaTime)
{
    if (!isActive)
        return;

    position.y -= tuning::playerShot::Speed * deltaTime;

    Rect hitbox = GetHitbox();
    if (hitbox.y + hitbox.height < 0.0f)
        Deactivate();
}

弾の管理はこれでいいの?

現在の実装では、無効な弾を削除せずに非アクティブにして再利用しています。
この方法でも特に問題はありませんが、弾の数が多くなると毎フレームの更新で無効な弾の分もループを回すことになり、パフォーマンスに影響する可能性があります。

一つの解決策として、無効になった弾を配列の末尾に移動させることでアクティブな弾だけを扱うようにする方法があります。

PlayerShotManager.hcpp
private:
    // アクティブな弾を先頭から順に格納する配列
    static constexpr std::size_t Capacity = 48;
    std::array<PlayerShot, Capacity> shots = {};

    // 配列の先頭から何発がアクティブか
    std::size_t activeShotCount = 0;
PlayerShotManager.cppcpp
void PlayerShotManager::Update(float deltaTime)
{
    std::size_t index = 0;

    while (index < activeShotCount)
    {
        // trueなら弾はまだアクティブなので、次の弾へ進む
        if (shots[index].Update(deltaTime))
        {
            ++index;
            continue;
        }

        // アクティブな弾の数を減らす
        --activeShotCount;

        // アクティブな末尾の弾と空いた弾を入れ替える
        // (今回の例ならswapではなくコピーでも問題ありません)
        if (index != activeShotCount)
            std::swap(shots[index], shots[activeShotCount]);
    }
}

効率が良いかは実行環境次第ですが、弾の数が多くなると差が出てくる可能性があります。
また、コードに慣れた人であれば、こちらの方が簡潔に見えるかもしれません。

実務においては様々なトレードオフを考慮しながら実装を選択することになります。
今回は直截的な実装を採用していますが、後者の方法も覚えておくと良いでしょう。

こうした変更を弾の管理クラスに閉じ込めることで、ゲームの他の部分に影響を与えずに改善できるのもオブジェクト指向の利点です。
機能と性能の両立を考える際には、こうした設計の工夫も重要です。

動作確認

次の順で動作を確認します。

  1. ZキーまたはSpaceキーを押して弾が上へ進むことを確認する。
  2. キーを押し続けて複数の弾が同時に表示されることを確認する。
  3. 画面外へ出た弾が消えた後、再び発射できることを確認する。
  4. プレイヤーを移動させながら発射し、弾が発射時点の位置から進むことを確認する。

第02回の完成確認

チェックリスト
  • 弾を複数発同時に表示できる
  • 弾が一定速度で上方向へ移動する
  • 画面外へ出た弾が停止し、後の発射で再利用される
  • Gameが弾の配列や個別の弾を直接操作していない