This explanation is currently available in Japanese.
ひとことで
弾や粒子、敵のように、頻繁に作っては消す物体を、あらかじめ決まった数だけ作っておき、使い回す方法です。撃つたびに新しいオブジェクトを作り、消えたら捨てていると、メモリの確保と解放が大量に発生します。ガベージコレクション(GC)のある言語(C#、JavaScript など)では、捨てたオブジェクトがごみとして溜まり、回収のためにゲームが一瞬止まることがあります。プールなら、消えた物体を「空き」に戻して次に使うので、実行中に新しくメモリを確保しません。
ゲームでの使いどころ
- 弾・弾幕:1 秒に何百発も撃っては消えるシューティング。
- エフェクト:火花、ヒット時の数字、足跡、薬莢、破片。
- 敵やアイテム:出現と退場をくり返す雑魚敵、コイン。
- 音:効果音の再生チャンネル(同時に鳴らせる数を決めておき、使い回す)。
- エンジンの機能:Unity の
ObjectPool<T>、Unreal の粒子システムの内部など。
しくみ
- 最初に作る:同時に存在する最大の数(撃つ速さ × 寿命)より少し多めの物体を作り、すべて「空き」のリストに入れます。
- 取り出す:物体が必要になったら、空きリストから 1 つ取り出し、位置や速度などの状態を初期化して「使用中」にします。
- 戻す:物体が消えたら、捨てずに「未使用」の印を付けて、空きリストへ戻します。
- 足りないとき:空きがなければ、①作らずにあきらめる(弾を出さない)、②いちばん古い物を再利用する、③プールを大きくする(そのときだけメモリを確保する)、のどれかを選びます。
空きリストは、配列の末尾から出し入れするスタックにすると、取り出しも戻しも一定の手間で済みます。
実装のポイント
- 状態を必ず初期化する:使い回した物体には、前に使ったときの状態(残りの寿命、色、当たった記録)が残っています。取り出すたびにすべて初期化します。
- 戻し忘れ・二重に戻す:戻し忘れるとプールが枯れ、同じ物体を 2 回戻すと、2 か所で同時に使われて不思議なバグになります。使用中の印で確かめます。
- 配列の中で済ませる:物体のデータを SoA の配列で持ち、「生きている数」だけを管理する方法もあります。消えた物体を配列の最後の物体と入れ替えれば、生きている物体を先頭に詰めたまま保てます。
- 最初の作成を読み込み時に:ゲームの開始時やロード画面でプールを作っておくと、プレイ中に確保が起きません。
つまずきやすい点
- 大きすぎるプール:使わない物体の分だけメモリを占有します。実際のピーク時の数を計測して決めます。
- 参照が残る:消えた弾を、ほかの処理(ホーミングのターゲットなど)が参照したままだと、使い回された別の弾を追いかけてしまいます。世代番号を持たせて、古い参照を見分けます。
- GC のない言語でも効く:C++ でも、確保と解放を大量にくり返すとメモリが断片化し、確保そのものの手間もかかるので、プールは有効です。
デモで試すこと
- 砲台が 1 秒に 400 発の弾を渦巻き状に撃っています。「オブジェクトプールで使い回す」では、右上の枠の図で、弾が枠に出入りする様子がわかります。右下のごみのグラフは 0 のままです。
- 「撃つたびに作って捨てる」に切り替えると、捨てた弾がごみとして溜まり(模式的な見積もり)、一定量でガベージコレクションが走ります(砲台が赤く光る)。
- 「プールの大きさ」を小さくすると、枠が足りずに撃てなかった弾が出ます。「1 秒に撃つ弾の数」を上げると、必要な枠の数が増えます(撃つ速さ × 寿命 2.6 秒)。