Object pooling is a performance optimization technique. Instead of destroying an object, you deactivate it and add it back to a pool. When needed again, you reuse it instead of creating a new one.
- No repeated Instantiate() and Destroy() calls
- Reduces garbage collection and lag spikes
- Essential for mobile games and bullet-hell shooters
Problem without Pooling
When you instantiate and destroy objects frequently, Unity keeps allocating and freeing memory. This causes lag spikes.
// Bad for performance - creates garbage
void Shoot()
{
GameObject bullet = Instantiate(bulletPrefab);
Destroy(bullet, 2f); // Creates garbage collection
}
Every time you destroy an object, Unity's garbage collector has to clean it up later, causing frame drops.
With Pooling
Create a pool of objects at start. Reuse them instead of destroying.
public class BulletPool : MonoBehaviour
{
public GameObject bulletPrefab;
public int poolSize = 20;
private Queue<GameObject> pool = new Queue<GameObject>();
void Start()
{
// Create all bullets at start
for (int i = 0; i < poolSize; i++)
{
GameObject bullet = Instantiate(bulletPrefab);
bullet.SetActive(false); // Deactivate
pool.Enqueue(bullet); // Add to queue
}
}
public GameObject GetBullet()
{
if (pool.Count > 0)
{
GameObject bullet = pool.Dequeue();
bullet.SetActive(true);
return bullet;
}
else
{
// Pool empty - create new (optional)
return Instantiate(bulletPrefab);
}
}
public void ReturnBullet(GameObject bullet)
{
bullet.SetActive(false);
pool.Enqueue(bullet);
}
}
Using the Object Pool
Here's how to shoot using the object pool.
public class Shooter : MonoBehaviour
{
public BulletPool bulletPool;
public Transform firePoint;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
Shoot();
}
}
void Shoot()
{
// Get bullet from pool
GameObject bullet = bulletPool.GetBullet();
bullet.transform.position = firePoint.position;
bullet.transform.rotation = firePoint.rotation;
// Set velocity
Rigidbody rb = bullet.GetComponent<Rigidbody>();
rb.velocity = firePoint.forward * 20f;
// Return to pool after 2 seconds
StartCoroutine(ReturnAfterDelay(bullet, 2f));
}
IEnumerator ReturnAfterDelay(GameObject bullet, float delay)
{
yield return new WaitForSeconds(delay);
bulletPool.ReturnBullet(bullet);
}
}
Generic Object Pool (Reusable)
Create one pool that works for any type of object.
public class ObjectPool<T> where T : Component
{
private T prefab;
private Queue<T> pool = new Queue<T>();
public ObjectPool(T prefab, int initialSize)
{
this.prefab = prefab;
for (int i = 0; i < initialSize; i++)
{
T obj = GameObject.Instantiate(prefab);
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
public T Get()
{
if (pool.Count > 0)
{
T obj = pool.Dequeue();
obj.gameObject.SetActive(true);
return obj;
}
else
{
return GameObject.Instantiate(prefab);
}
}
public void Return(T obj)
{
obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
Usage:
public class GameManager : MonoBehaviour
{
public GameObject bulletPrefab;
private ObjectPool<Bullet> bulletPool;
void Start()
{
bulletPool = new ObjectPool<Bullet>(bulletPrefab.GetComponent<Bullet>(), 30);
}
}
When to Use Object Pooling
Good for
- Bullets in shooting games.
- Enemies that spawn frequently.
- Particle effects (explosions, dust).
- Pickups (coins, health items).
Not needed for
- UI elements.
- Bosses (spawn rarely).
- Scene objects.
- Static environment.
Without Pooling vs With Pooling
| Feature | Without Pooling | With Pooling |
|---|---|---|
| Object Handling | Uses Instantiate() and Destroy() repeatedly | Reuses objects (activate/deactivate) |
| Performance Impact | Causes garbage collection → lag spikes | Avoids garbage collection overhead |
| Speed | Slower | Much faster |
| Implementation | Simple to implement | Requires extra setup/code |