Unityのオブジェクトプールは、弾やエフェクトのように何度も出し入れするオブジェクトを、毎回作って消すのではなく、使い終わったら取っておいて使い回す仕組みです。
Unityには、そのための ObjectPool<T> というクラスが UnityEngine.Pool に用意されています。
- 取り出す:pool.Get()(空なら新しく作る)
- 返す:pool.Release(オブジェクト)(Destroyの代わりに呼ぶ)
- 作り方・取り出し時・返却時・破棄時の処理を、コンストラクターに関数で渡す
- 多くの場合、取り出すときにSetActive(true)、返すときにSetActive(false)にする
この記事は、Unity 6.3 LTSのスクリプトリファレンスと公式マニュアル「Pooling and reusing objects」の記載(2026年10月18日に確認)に基づきます。
Unityのエディタでの実機での再現は含まず、掲載したコードもUnityでの実行は確かめていません。
オブジェクトプールを使う理由
公式マニュアルでは、オブジェクトプールは、作成と破棄をくり返すことによる負荷を減らし、メモリの確保と解放の回数を抑えると説明されています。
その結果、ガベージコレクションの負荷とCPUの負荷が下がるとされています。
マニュアルでは、弾のように何度も出てくるが、同時に画面にあるのは少しだけ、というオブジェクトが例に挙がっています。
撃つたびにPrefabからInstantiateするのではなく、最初にプールを作っておき、必要なときに取り出して使い回します。
ObjectPoolの作り方
ObjectPool<T> は、コンストラクターに次の値を渡して作ります。
| 引数 | 役割 |
|---|---|
| createFunc | プールが空のときに、新しいオブジェクトを作る関数 |
| actionOnGet | プールから取り出したときに呼ばれる。準備をして有効にする |
| actionOnRelease | プールに返したときに呼ばれる。後片付けをして無効にする |
| actionOnDestroy | プールが最大数に達していて返せないときに呼ばれる。ここで破棄する |
| collectionCheck | trueにすると、既にプールにある物をもう一度返したときに例外を出す |
| defaultCapacity | 最初に用意する入れ物の大きさ |
| maxSize | プールに置いておける最大の数 |
スクリプトリファレンスでは、defaultCapacityの数だけ最初にオブジェクトが作られるわけではなく、Getを呼んだときに1つずつ作られると説明されています。
maxSizeに達した後に返されたオブジェクトはプールに入らず、actionOnDestroyで破棄されます。
collectionCheckによる検出はエディタの中だけで行われ、ビルドしたアプリでは行われません。
公式マニュアルでは、検出のための負荷はあるものの、開発中はtrueにしておくことが勧められています。
弾を使い回す例
次の例は、一定の間隔で弾を出し、2秒たったらプールに返すものです。
公式マニュアルの弾の例と同じく、弾に自分のプールを持たせ、弾が自分で返る形にしています。
弾のスクリプト
using UnityEngine;
using UnityEngine.Pool;
public class Bullet : MonoBehaviour
{
[SerializeField] float speed = 10f;
[SerializeField] float lifeTime = 2f;
IObjectPool<Bullet> pool;
float timer;
// どのプールに返すかを、作ったときに受け取る
public void SetPool(IObjectPool<Bullet> owner)
{
pool = owner;
}
// 取り出すたびに、位置と経過時間を初めの状態に戻す
public void ResetState(Vector3 position, Quaternion rotation)
{
transform.SetPositionAndRotation(position, rotation);
timer = 0f;
}
void Update()
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
timer += Time.deltaTime;
if (timer >= lifeTime)
{
pool.Release(this); // Destroyの代わりにプールへ返す
}
}
}弾を出すスクリプト
using UnityEngine;
using UnityEngine.Pool;
public class BulletSpawner : MonoBehaviour
{
[SerializeField] Bullet bulletPrefab;
[SerializeField] float interval = 0.2f;
ObjectPool<Bullet> pool;
float nextTime;
void Awake()
{
pool = new ObjectPool<Bullet>(
createFunc: () =>
{
var bullet = Instantiate(bulletPrefab);
bullet.SetPool(pool);
return bullet;
},
actionOnGet: bullet => bullet.gameObject.SetActive(true),
actionOnRelease: bullet => bullet.gameObject.SetActive(false),
actionOnDestroy: bullet => Destroy(bullet.gameObject),
collectionCheck: true,
defaultCapacity: 20,
maxSize: 100);
}
void Update()
{
if (Time.time < nextTime) return;
nextTime = Time.time + interval;
var bullet = pool.Get();
bullet.ResetState(transform.position, transform.rotation);
}
}BulletSpawnerを空のゲームオブジェクトに付け、Bulletを付けたPrefabを Bullet Prefab の欄に入れて再生します。
このコードは、スクリプトリファレンスと公式マニュアルの例をもとに書いたもので、Unityでの実行は確かめていません。
返すときに状態を戻す
プールから取り出した物は、前に使ったときの位置や速度などが残っています。
公式マニュアルでは、次に取り出したときに初めて使うときと同じ状態から始まるように、状態を戻すことが大切だと説明されています。
戻す場所は、actionOnGetでもactionOnReleaseでもよく、プールされる側のスクリプトに専用の関数を作って呼ぶ方法も挙がっています。
返すときによく行う後片付けとして、マニュアルでは次のものが挙がっています。
- コルーチンを止める
- イベントの登録を外す
- Rigidbodyの速度などの物理の状態を戻す
- アニメーションを初めの状態に戻す
- パーティクルシステムを止める
- ゲームオブジェクトを無効にして、プールの中でUpdateが呼ばれないようにする
公式マニュアルの弾の例では、返す前にRigidbodyの linearVelocity と angularVelocity を0にしています。
よくある間違い
| 間違い | 起きること・直し方 |
|---|---|
| 返すときにDestroyしている | 使い回せず、毎回作り直しになる。Destroyの代わりにReleaseを呼ぶ |
| 同じ物を2回Releaseしている | collectionCheckがtrueなら、エディタで例外が出る。返した後はその物を使わない |
| 取り出した物の状態を戻していない | 前に使ったときの位置や速度のまま動き出す |
| 返すときに無効にしていない | プールの中でもUpdateが呼ばれ続ける |
プールの状態は、CountActive(使用中の数)、CountInactive(プールで待っている数)、CountAll(合計)で確かめられます。
数が増え続ける場合は、Releaseを呼び忘れている物がないかを見直します。
シーンを切り替えるときの注意
公式マニュアルでは、プールは作られたシーンに結び付いていて、そのシーンが破棄されると、プールとプールの中の物もまとめて破棄されると説明されています。
シーンをまたいでプールを残す場合は、プールを管理するゲームオブジェクトにDontDestroyOnLoadを使い、返すときにSetParentでその下へ戻す方法が案内されています。
シーンを破棄するときに使用中の物がある場合は、OnDisableやOnDestroyで自分を返すようにするか、SceneManager.activeSceneChangedで回収するよう勧められています。
ステージの切り替えなどで待っている物をまとめて破棄したいときは、Clearを使います。
シーンの読み込み方は、Unityでシーンを切り替える方法で説明しています。
ゲームオブジェクト以外もプールできる
公式マニュアルでは、ListやDictionaryのようなコレクションも、プールして使い回せると説明されています。
UnityEngine.Poolには、List<T>、HashSet<T>、Dictionary<TKey, TValue>のためのプールも用意されています。
毎フレーム new List<T>() をしている処理は、クラスのフィールドに1つ持ってClearで使い回すだけでも、メモリの確保を減らせるとされています。
マニュアルでは、UnityEngine.Poolの機能はスレッドセーフではなく、メインスレッドから呼ぶ必要があると注意書きがあります。
