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> は、自作よりも安全で柔軟、そして長期的な保守に優れた選択肢です。

特にチーム開発や講義教材では、再利用と拡張性を重視した構成を意識することで、実装の質も学びの深さも大きく向上します。


訪問数 4 回, 今日の訪問数 4回