Unity標準 ObjectPool の使い方と自作との違い
〜2021以降の開発で「Instantiate地獄」を脱する〜
はじめに
Unity開発でよくある問題のひとつが、次のようなパフォーマンス低下です。
敵・弾・エフェクトなどを Instantiate() / Destroy() で毎回生成していると、GC(ガベージコレクション)によるフレーム落ちが発生する。
こうした「生成・破棄の繰り返し」を防ぐために使われるのが、Object Pool(オブジェクトプール)パターンです。
Unity 2021以降では、UnityEngine.Pool 名前空間に 標準の ObjectPool<T> クラス が追加され、これまで自作していたプール処理を安全かつ簡潔に書けるようになりました。
1. Object Poolとは
Object Poolとは、使い終わったオブジェクトを破棄せずに再利用する仕組みです。

従来の実装(自作Queue方式)
readonly Queue<GameObject> pool = new();
public GameObject Get()
{
    if (pool.Count > 0)
    {
        var obj = pool.Dequeue();
        obj.SetActive(true);
        return obj;
    }
    return Instantiate(prefab);
}
public void Release(GameObject obj)
{
    obj.SetActive(false);
    pool.Enqueue(obj);
}
シンプルですが、生成数・破棄タイミング・上限制御などをすべて自分で管理する必要がありました。
2. Unity標準 ObjectPool<T> の登場
Unity 2021.1 以降では、using UnityEngine.Pool; に含まれる ObjectPool<T> が正式実装されました。
これにより、同様の処理を数行で安全に書けます。
基本構文
using UnityEngine;
using UnityEngine.Pool;
public class BulletShooter : MonoBehaviour
{
    [SerializeField] GameObject bulletPrefab;
    ObjectPool<GameObject> pool;
    void Awake()
    {
        pool = new ObjectPool<GameObject>(
            createFunc: () => Instantiate(bulletPrefab),
            actionOnGet: b => b.SetActive(true),
            actionOnRelease: b => b.SetActive(false),
            actionOnDestroy: b => Destroy(b),
            collectionCheck: false,   // 重複チェック(高速化のためfalse)
            defaultCapacity: 10,      // 初期確保数
            maxSize: 50               // 最大保持数
        );
    }
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            var bullet = pool.Get();
            bullet.transform.position = transform.position;
            // 一定時間でプールに戻す
            StartCoroutine(ReleaseAfterTime(bullet, 1.5f));
        }
    }
    IEnumerator ReleaseAfterTime(GameObject bullet, float time)
    {
        yield return new WaitForSeconds(time);
        pool.Release(bullet);
    }
}
動作イメージ

3. 各パラメータの意味
| 引数 | 内容 | 
|---|---|
| createFunc | 新しい要素を作成する処理 | 
| actionOnGet | プールから取り出したときに実行する処理 | 
| actionOnRelease | プールに戻すときの処理 | 
| actionOnDestroy | プール容量を超えたときの破棄処理 | 
| collectionCheck | 重複登録チェック(デバッグ向け) | 
| defaultCapacity | 初期確保数 | 
| maxSize | 保持できる最大数 | 
4. 自作との違い・メリット比較
| 項目 | 自作プール | Unity標準 ObjectPool<T> | 
|---|---|---|
| 実装量 | 多い(Queue管理が必要) | 少ない(数行で完結) | 
| 型対応 | GameObject限定が多い | どんな型でも使える(ジェネリック) | 
| メモリ管理 | 手動 | 自動(上限超過時に破棄) | 
| デバッグ | 独自で実装 | 内部統計・安全チェック付き | 
| 速度 | 高速(単純構造) | わずかに低下するが安定性あり | 
5. さらに軽量な LinkedPool<T>
LinkedPool<T> は同じ名前空間にある軽量プールです。
内部的に連結リストを使用しており、GC発生をより抑えたい場合に有効です。
using UnityEngine.Pool;
LinkedPool<GameObject> linkedPool = new LinkedPool<GameObject>(
    createFunc: () => Instantiate(prefab),
    actionOnRelease: b => b.SetActive(false)
);
特徴:
- より低コスト(GC削減)
 - Destroy処理やmaxSize制限なし(明示的に管理)
 
6. 実践的な使い分け
| 用途 | 推奨パターン | 理由 | 
|---|---|---|
| 弾丸・敵・エフェクト | ObjectPool<GameObject> | 上限数制御と安全性を両立 | 
| 短命UI(ポップアップ等) | LinkedPool<GameObject> | 軽量で高速 | 
| システム系(C#オブジェクト) | ObjectPool<T> | 型に依存しない汎用性 | 
7. Tips:Unity 6での補足
Unity 6(6000.x系)では、このAPIがさらに安定化しており、collectionCheck 無効時でも安全な動作が保証されています。
ゲームループ内で頻繁に生成されるオブジェクトに対しても、GC発生をほぼゼロに抑えられます。
まとめ
| 比較項目 | 自作プール | Unity標準 ObjectPool<T> | 
|---|---|---|
| 実装コスト | 高い | 低い | 
| 安定性 | 中 | 高 | 
| 柔軟性 | 中 | 高 | 
| 対象 | GameObject中心 | 任意の型(構造体もOK) | 
おわりに
Unity標準の ObjectPool<T> は、自作よりも安全で柔軟、そして長期的な保守に優れた選択肢です。
特にチーム開発や講義教材では、再利用と拡張性を重視した構成を意識することで、実装の質も学びの深さも大きく向上します。





ディスカッション
コメント一覧
まだ、コメントがありません