2 / 4 停止中の弾を再利用する
弾を配列に用意し、発射のたびに停止中の1発を使います。
弾を置く場所を先に用意する
PlayerShotManagerは48発分の弾を持ちます。発射時は停止中の弾を使い、画面外へ出た弾は停止状態に戻します。このように、用意したオブジェクトを使い回す仕組みをオブジェクトプール(Object Pool)と呼びます。
提供済みのクラス宣言を確認する
開始用のSource/Shooting/PlayerShotManager.hとPlayerShotManager.cppを開いてください。宣言とReset・Update・Drawは実装済みです。配列の所有と各関数の役割を読み、Spawnの未実装部分を記述します。
#pragma once
#include <array>
#include <cstddef>
#include "Shooting/PlayerShot.h"
namespace dxgame::shooting
{
class ImageManager;
// プレイヤー弾を固定数所有し生成、更新、描画、再利用を管理する
class PlayerShotManager
{
public:
// 停止中の弾の中心をstartPositionに合わせて生成する
// 空きがない場合は生成せずfalseを返す
bool Spawn(const Vector2& startPosition);
// 所有するすべてのプレイヤー弾を更新する
// deltaTimeは1フレームの経過秒数
void Update(float deltaTime);
// 所有するすべてのプレイヤー弾を描く
void Draw(const ImageManager& images) const;
// 所有するすべてのプレイヤー弾を停止して空の状態へ戻す
void Reset();
private:
// 停止中の要素を再利用しゲーム中にはメモリを確保しない
static constexpr std::size_t Capacity = 48;
std::array<PlayerShot, Capacity> shots = {};
};
}std::array<PlayerShot, Capacity>は、PlayerShotをCapacity個並べた固定長配列です。Capacityはこのクラスで扱う上限数です。
空いている弾を探す
Spawn()は配列を先頭から調べ、最初に見つかった停止中の弾を発射します。発射できればtrue、全弾が使用中ならfalseを返します。
bool PlayerShotManager::Spawn(const Vector2& startPosition)
{
// 先頭から停止中の弾を探して再利用する
for (PlayerShot& shot : shots)
{
if (shot.IsActive())
continue;
shot.Spawn(startPosition);
return true;
}
return false;
}PlayerShot& shotの&は参照を表します。shot.Spawn()による変更は、配列内の弾オブジェクトへ直接反映されます。
満杯の場合も試す
この確認は、ステップ4で移動と画面外停止を実装してから行います。今はビルドと、空きがない場合にfalseへ進む処理を確認します。
ステップ4の終了後、一時的にCapacityを2に変更して連射を試します。2発が画面内にある間は新しい弾が出ず、1発が画面外へ消えると次の弾を発射できることを確認します。確認後は48に戻します。
発展:メモリの確保と再利用
C++には、必要なときに領域を確保する方法もあります。確保と解放を頻繁に繰り返す場合は、処理にかかる時間と領域の管理を考える必要があります。プールは、確保済みの領域を再利用する方法です。
ただし、性能は処理内容によって異なります。領域の確保方法だけで速さが決まるわけではありません。要素数がデータから決まる場合は、std::vectorなども使い分けます。
- Spawnが最初に見つけた停止中の弾だけを発射する理由を説明できる
- 全弾が使用中の場合は
Spawn()がfalseを返す - 画面外での停止と再利用の実行確認はステップ4の後に行うと分かる