Object Pool
Generic object pooling system to reduce garbage collection. Perfect for bullets, particles, and enemies.
How to Use
Create a GameObject named 'ObjectPool'
Attach this script and define your pools in the inspector
Spawn: ObjectPool.Instance.Spawn("bullet", position, rotation)
Despawn: ObjectPool.Instance.Despawn("bullet", gameObject)
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
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();
}