Unity初心者でもわかるObjectPool(オブジェクトプール)入門チュートリアル

目的

このチュートリアルでは、弾を撃つたびに Instantiate() と Destroy() を呼んでしまうことで起きるフレーム落ちやメモリ負荷を、ObjectPool(オブジェクトプール) を使って解決する方法を学びます。

最終的には次のような仕組みを完成させます。

  • スペースキーで弾を発射できる
  • 弾は一定時間で消える(実際は再利用)
  • 無駄な生成・破棄がなくなる

Step 1. 新しいシーンを作成する

  1. Unityを開き、新しいシーンを作る(例:PoolSample)
  2. Main Camera と Directional Light はそのままで構わない
  3. 空のオブジェクトを作成して「Player」にリネーム
  4. Playerにスクリプトを追加する

Step 2. まずは普通に弾を撃つ

プールを使わない基本的な仕組みを確認します。

using UnityEngine;

public class SimpleShooter : MonoBehaviour
{
    [SerializeField] GameObject bulletPrefab;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            GameObject bullet = Instantiate(bulletPrefab, transform.position, Quaternion.identity);
            Destroy(bullet, 1f);
        }
    }
}

このコードでは、弾を撃つたびに生成と破棄が行われます。

繰り返すとメモリ確保・解放の負荷が増えます。


Step 3. Unity標準のObjectPoolを導入する

3-1. 名前空間を追加

スクリプトの先頭に次を追記します。

using UnityEngine.Pool;

3-2. Playerに新しいスクリプトを作成

次のコードを PooledShooter.cs としてPlayerにアタッチします。

using UnityEngine;
using UnityEngine.Pool;
using System.Collections;

public class PooledShooter : 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,
            defaultCapacity: 10,
            maxSize: 50
        );
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            var bullet = pool.Get();
            bullet.transform.position = transform.position;
            StartCoroutine(ReleaseAfterTime(bullet, 1f));
        }
    }

    IEnumerator ReleaseAfterTime(GameObject bullet, float t)
    {
        yield return new WaitForSeconds(t);
        pool.Release(bullet);
    }
}

Step 4. 弾のプレハブを用意する

  1. Hierarchyで「3D Object → Sphere」を作成
  2. 名前を「Bullet」に変更
  3. Rigidbodyを追加し、Use Gravityをオフにする
  4. Scaleを (0.2, 0.2, 0.2) に調整
  5. ProjectビューにドラッグしてPrefab化
  6. Hierarchyから削除してよい

Step 5. 実行テスト

  1. Playerオブジェクトのスクリプト欄にある「Bullet Prefab」に先ほどのBulletを割り当てる
  2. 再生ボタンを押し、スペースキーを押して弾を発射する
  3. 弾は1秒後に見えなくなるが、内部ではDestroyされず再利用されている

Step 6. パフォーマンス比較

状況実装方法挙動
BeforeInstantiate/Destroy毎回メモリ確保と解放を行うためGCが頻発
AfterObjectPool一度作った弾を再利用し、GCの発生を抑制

Profilerで実行すると、GC Allocのスパイクが減少していることを確認できます。


Step 7. 応用例

内容実装方法
弾を前方に飛ばすbullet.GetComponent<Rigidbody>().velocity = transform.forward * 10f;
弾の種類を増やすプールを種類ごとに分ける
敵やエフェクトにも利用同じ仕組みで再利用可能
より軽量化したいLinkedPool<T> に変更する

まとめ

比較項目Instantiate/DestroyObjectPool
パフォーマンス遅くなる高速で安定
メモリ効率毎回確保・破棄再利用
実装量少ない少し多いが管理が楽
向いている用途テスト・小規模実際のゲーム開発

補足

  • ObjectPool<T> は Unity 2021 以降で利用可能
  • 名前空間 UnityEngine.Pool を忘れずに追加する
  • Get() で取り出し、Release() で戻すのが基本動作
  • 弾・敵・エフェクトなどの一時オブジェクトに最適

練習課題

  1. 弾を前方向に発射するコードを追加する
  2. 一定距離で戻る処理を実装する
  3. 敵を一定間隔で出現させ、倒したらプールに戻す

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