Object Pooling
Object Pooling は毎回の Instantiate/Destroy の代わりにインスタンスを再利用します。弾・FX・敵ウェーブなどで GC スパイクとカクつきを抑え、フレームを安定させます。図解とシミュレーターでプールの流れと負荷差を比較しながら、実践的にしっかり学べます。自学にも最適です。
想像してみてください...
毎回新しいコーヒーカップを買って → 使って → 捨てる(Instantiate → 使用 → Destroy)代わりに、レストランは食器プールを使います: 20 杯用意し、1 つ取り、ラックに返す。買い足しも廃棄もなし——Garbage Collection ゼロ。Unity では GC.Collect() がラッシュ時に厨房へ突入する掃除人——ドロップフレームです。Pooling がそれを止めます。
概念の詳細
Instantiate/Destroy の問題: 各
Instantiate()
は新しい GameObject を確保し、コンポーネントデータをコピーし、シーンに登録します。
Destroy()
はメモリを GC 対象にします。シューターは毎秒 30–50 発——頻繁な GC が典型的なカクつき(数秒ごとの「突然のラグ」)を生みます。
Object Pool パターン: ロード時に固定数を事前生成。必要時は生成ではなくプールから取り出し(非アクティブだったもの)。使い終わったら無効化して戻す。メモリは平坦で、GC は走りません。
Unity 2021+ から
UnityEngine.Pool
が
ObjectPool<T>、
LinkedPool<T>、
GenericPool<T>
を同梱——手書きキュー不要。コールバック: onCreate、onGet、
onRelease、onDestroy(プール満杯時)。
典型用途: 弾、ヒット/爆発 FX、浮かぶダメージ数字、敵/NPC スポーン、足音 AudioSource、UI トースト、パーティクル。1 分に 1 回未満しか出ないオブジェクトはプール不要——管理オーバーヘッドが割に合いません。
プールの仕組み
ハンズオン手順
IPoolable インターフェースを作る
OnGet()(プールから取るときの状態リセット)と OnRelease()(戻す前のクリーンアップ)。
ObjectPool を作成
new ObjectPool<T>(onCreate, onGet, onRelease, onDestroy, collectionCheck, defaultCapacity, maxSize) —— 2021+ 標準。
pool.Get()/pool.Release() を使う
Instantiate を pool.Get()、Destroy を pool.Release() に置き換え。onGet で位置/速度/状態をリセット。
ロード時にプールをプリウォーム
開始時に N 個を生成してすぐ Release。空プールでの最初のフレームスパイクを防ぎます。
pool.CountAll/pool.CountActive を監視
統計をログしてプールサイズが足りるか確認。countActive/countAll が高い → defaultCapacity を上げる。
インタラクティブシミュレーター
Instantiate/Destroy と Object Pool を比較——Fire を押して GC の差を確認しましょう。
Pool: 5/12
Allocations
0
GC Events
0
Active Objects
0
コード例
基本Queue によるシンプルな弾プール——Unity パッケージ不要。
using UnityEngine;
using System.Collections.Generic;
public class BulletPool : MonoBehaviour
{
public GameObject bulletPrefab;
public int poolSize = 30;
readonly Queue<GameObject> pool = new Queue<GameObject>();
void Awake()
{
// Pre-warm: create and disable
for (int i = 0; i < poolSize; i++)
{
var go = Instantiate(bulletPrefab);
go.SetActive(false);
go.transform.SetParent(transform);
pool.Enqueue(go);
}
}
public GameObject Get(Vector3 pos, Quaternion rot)
{
var go = pool.Count > 0
? pool.Dequeue()
: Instantiate(bulletPrefab); // Expand if empty
go.transform.SetPositionAndRotation(pos, rot);
go.SetActive(true);
return go;
}
public void Release(GameObject go)
{
go.SetActive(false);
pool.Enqueue(go);
}
}コード例
上級UnityEngine.Pool.ObjectPool<T>(Unity 2021+)——標準の自動拡張とコールバック。
using UnityEngine;
using UnityEngine.Pool;
public class AdvancedBulletPool : MonoBehaviour
{
public Bullet bulletPrefab;
ObjectPool<Bullet> pool;
void Awake()
{
pool = new ObjectPool<Bullet>(
createFunc: () => Instantiate(bulletPrefab, transform),
actionOnGet: b => { b.gameObject.SetActive(true); b.OnGet(); },
actionOnRelease: b => { b.gameObject.SetActive(false); b.OnRelease(); },
actionOnDestroy: b => Destroy(b.gameObject), // When the pool is full
collectionCheck: false, // Skip duplicate checks (production)
defaultCapacity: 20,
maxSize: 50
);
}
public Bullet Spawn(Vector3 pos, Vector3 dir)
{
var bullet = pool.Get();
bullet.transform.position = pos;
bullet.Init(dir, pool); // Bullet calls pool.Release(this) when done
return bullet;
}
public void LogStats()
{
Debug.Log($"Pool: {pool.CountActive} active / {pool.CountAll} total");
}
}📌 要点
- ▸Pool = オブジェクト再利用、GC アロケーション回避
- ▸ロード時にプリウォームして初フレームスパイクを防ぐ
- ▸UnityEngine.Pool.ObjectPool<T> —— 2021+ 標準
- ▸状態リセットは onGet、Update ではない
- ▸collectionCheck=true は Editor のデバッグ専用
⚠️ よくあるミス
❌ すでにアクティブなオブジェクトを Release(二重リリース)
二重 Release → プール破損 → ちらつきやクラッシュ
✅ 開発時は collectionCheck=true、Release() 前に null ガード
❌ Get 時に状態をリセットしない
前回の速度・色・HP が残る
✅ actionOnGet コールバックで完全にリセットする