intermediate Utilities

Object Pool

Generic object pooling system to reduce garbage collection. Perfect for bullets, particles, and enemies.

Unity 2022.3+ · 2.1 KB · ObjectPool.cs

How to Use

1

Create a GameObject named 'ObjectPool'

2

Attach this script and define your pools in the inspector

3

Spawn: ObjectPool.Instance.Spawn("bullet", position, rotation)

4

Despawn: ObjectPool.Instance.Despawn("bullet", gameObject)

5

Optionally implement IPoolable on your pooled objects for callbacks

Features

  • Tag-based pool dictionary for multiple object types
  • Automatic pool expansion when all instances are busy
  • IPoolable interface for OnSpawn/OnDespawn lifecycle callbacks
  • Inspector-configurable pools with prefab, tag, and initial size
  • Singleton pattern with DontDestroyOnLoad for cross-scene use
  • Pre-instantiates objects at startup to avoid runtime allocation spikes

When to Use This

Critical for shooters, bullet-hell games, tower defense, and any game that spawns and destroys many objects frequently. Use this to eliminate garbage collection stutters from Instantiate/Destroy cycles on bullets, particles, enemies, and projectiles. Especially important for mobile games where GC spikes cause noticeable frame drops.

Common Mistakes

Forgetting to call Despawn() and using Destroy() instead will permanently remove objects from the pool, eventually causing auto-expansion every spawn. The pool tag string must match exactly between Spawn() and Despawn() calls — a typo creates a new pool silently. Make sure pooled objects reset their own state in OnSpawn() via the IPoolable interface, or they will retain data from their previous life.

Source Code

ObjectPool.cs
C#
using UnityEngine;
using System.Collections.Generic;

public class ObjectPool : MonoBehaviour
{
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int initialSize = 10;
    }

    public static ObjectPool Instance { get; private set; }

    [SerializeField] private List<Pool> pools;

    private Dictionary<string, Queue<GameObject>> poolDictionary;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }

        poolDictionary = new Dictionary<string, Queue<GameObject>>();

        foreach (var pool in pools)
        {
            var queue = new Queue<GameObject>();
            for (int i = 0; i < pool.initialSize; i++)
            {
                GameObject obj = Instantiate(pool.prefab, transform);
                obj.SetActive(false);
                queue.Enqueue(obj);
            }
            poolDictionary[pool.tag] = queue;
        }
    }

    public GameObject Spawn(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictionary.ContainsKey(tag))
        {
            Debug.LogWarning($"Pool '{tag}' doesn't exist.");
            return null;
        }

        var queue = poolDictionary[tag];
        GameObject obj;

        if (queue.Count > 0)
        {
            obj = queue.Dequeue();
        }
        else
        {
            // Find the prefab and create a new instance
            Pool pool = pools.Find(p => p.tag == tag);
            obj = Instantiate(pool.prefab, transform);
        }

        obj.transform.SetPositionAndRotation(position, rotation);
        obj.SetActive(true);

        if (obj.TryGetComponent<IPoolable>(out var poolable))
            poolable.OnSpawn();

        return obj;
    }

    public void Despawn(string tag, GameObject obj)
    {
        if (obj.TryGetComponent<IPoolable>(out var poolable))
            poolable.OnDespawn();

        obj.SetActive(false);

        if (poolDictionary.ContainsKey(tag))
            poolDictionary[tag].Enqueue(obj);
    }
}

public interface IPoolable
{
    void OnSpawn();
    void OnDespawn();
}