The Object Pooling system provides a high-performance solution for managing frequently instantiated and destroyed objects in Unity. It pre-allocates objects during initialization and reuses them throughout the game lifecycle, eliminating costly instantiation/destruction operations that cause performance spikes and garbage collection overhead.
Key Features:
- Asynchronous initialization with warm-up support
- Type-safe generic pooling (GameObject, ParticleSystem, AudioSource, custom components)
- Addressables and Resources integration
- Additive scene support with dynamic reparenting
- Auto-growth with configurable limits
- Specialized VFX/SFX convenience methods with auto-return
When to Use:
- Particle systems (explosions, hits, trails, muzzle flashes)
- Recurring effects spawned multiple times per second
- Effects with predictable lifetimes
Benefits:
- Eliminates instantiation hitches during combat
- Prevents GC spikes from destroyed particle systems
- Auto-returns to pool after particle duration
Example:
// Get VFX from pool (auto-returns after particle duration)
var explosion = poolManager.GetVFX("ExplosionVFX", hitPosition, Quaternion.identity);When to Use:
- One-shot audio clips (UI clicks, footsteps, gunshots)
- Spatial 3D audio sources
- Multiple simultaneous sounds
Benefits:
- Prevents audio source exhaustion
- Reduces overhead from AudioSource component creation
- Auto-cleanup after playback
Example:
// Play SFX with auto-return after 2 seconds
poolManager.PlaySFX("GunfireSFX", gunfireClip, volume: 0.8f, delayReturn: 2f);When to Use:
- Bullets, arrows, grenades
- Enemy spawners with wave-based systems
- Collectibles (coins, pickups)
Example:
// Get bullet from pool
var bullet = poolManager.Get<Bullet>("BulletPool");
if (bullet != null)
{
bullet.transform.position = barrelPosition;
bullet.Launch(direction, speed);
}
// Later, return to pool
poolManager.Return("BulletPool", bullet);When to Use:
- Damage numbers, floating text
- Inventory item icons
- Dynamic lists with frequent updates
Implement IPoolable for custom initialization/cleanup logic:
using PoolSolution.Interfaces;
using UnityEngine;
public class Bullet : MonoBehaviour, IPoolable
{
private Rigidbody rb;
public GameObject PoolableObject => gameObject;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
// Called when object is retrieved from pool
public void GetFromPool()
{
gameObject.SetActive(true);
rb.velocity = Vector3.zero;
rb.angularVelocity = Vector3.zero;
}
// Called when object is returned to pool
public void ReturnToPool()
{
rb.velocity = Vector3.zero;
gameObject.SetActive(false);
}
public void Launch(Vector3 direction, float speed)
{
rb.AddForce(direction * speed, ForceMode.Impulse);
}
}Unity Setup:
- Create prefab (e.g.,
Bullet.prefab) - Attach your IPoolable script
- Configure components (Rigidbody, Collider, etc.)
- Add to Addressables or place in Resources folder
For prefabs without IPoolable, the pool handles basic activation/deactivation:
Unity Setup:
- Create prefab (e.g.,
CoinPickup.prefab) - Add required components (MeshRenderer, Collider, etc.)
- Add to Addressables or Resources folder
The pool automatically:
- Activates GameObject on
Get() - Deactivates and resets transform on
Return()
Unity Setup:
- Create prefab with ParticleSystem component
- Configure particle settings (duration, emission, etc.)
- Set Stop Action to "Disable" (prevents auto-destruction)
- Add to Addressables with
PoolType.VFX
The pool automatically:
- Plays particles on
GetVFX() - Stops and clears particles on return
- Auto-returns after
ParticleSystem.main.duration
Unity Setup:
- Create empty GameObject with AudioSource component
- Add PoolableAudioSource component (required for pooling)
- Configure AudioSource settings (spatial blend, min/max distance)
- Leave
clipempty (assigned at runtime) - Add to Addressables with
PoolType.SFX
The pool automatically:
- Wraps AudioSource in PoolableAudioSource component
- Assigns clip and plays on
PlaySFX() - Stops and clears clip on return
- Auto-returns after specified delay
Important: All pooled AudioSource prefabs MUST have the PoolableAudioSource component attached.
- Right-click in Project window
- Navigate to Create > Optimization > Pooling > Pool Database
- Name it (e.g.,
MainPoolDatabase)
In the PoolDatabase inspector, add pool configurations:
Key: "BulletPool"
Initial Size: 50
Max Growth Size: 100
Allow Growth: ✓
Prefab Address: "Prefabs/Gameplay/Bullet" (Addressables path)
Prefab Reference: [Leave empty if using Addressables]
Parent: [Optional - Transform for pooled objects]
Type: Object
Key: "ExplosionVFX"
Initial Size: 10
Max Growth Size: 20
Allow Growth: ✓
Prefab Address: "VFX/Particles/Explosion"
Type: VFX
Key: "GunfireSFX"
Initial Size: 5
Max Growth Size: 10
Allow Growth: ✓
Prefab Address: "Audio/Sources/GenericAudioSource"
Type: SFX
- Create empty GameObject named
PoolManager - Add ObjectPoolManager component
- Assign your PoolDatabase asset
- (Optional) Assign Default Parent transform
ObjectPoolManager
├─ Pool Database: MainPoolDatabase
└─ Default Parent: [Optional Transform]
using PoolSolution.Runtime;
using UnityEngine;
public class WeaponController : MonoBehaviour
{
[SerializeField] private ObjectPoolManager poolManager;
public void Fire()
{
var bullet = poolManager.Get<Bullet>("BulletPool");
bullet.transform.position = barrelTransform.position;
bullet.Launch(transform.forward, bulletSpeed);
}
}using _Pawnshop.Optimization.Pooling;
public class VFXSpawner : MonoBehaviour
{
private IPoolService poolService;
private void Awake()
{
poolService = FindObjectOfType<PoolServiceWrapper>();
}
public void SpawnHitEffect(Vector3 position)
{
poolService.GetVFX("HitVFX", position, Quaternion.identity);
}
}| Parameter | Type | Description |
|---|---|---|
| key | string |
Unique identifier for pool lookup |
| initialSize | int |
Objects pre-allocated during warm-up |
| maxGrowthSize | int |
Maximum total objects (0 = unlimited) |
| allowGrowth | bool |
Allow dynamic creation when pool exhausted |
| prefabAddress | string |
Addressables key or Resources path |
| prefabReference | GameObject |
Direct prefab reference (overrides address) |
| parent | Transform |
Parent transform for pooled objects |
| type | PoolType |
Object/VFX/SFX (enables specialized behavior) |
- Warm-up pools during loading screens - Initialize pools asynchronously before gameplay
- Size pools appropriately - Monitor pool exhaustion logs and adjust sizes
- Use Addressables for large assets - Reduces memory footprint during build
- Implement IPoolable for complex objects - Custom reset logic prevents state leaks
- Avoid holding pool references - Always return objects when done
- Use type-specific pools - VFX/SFX types enable auto-return and cleanup
The pooling system is designed for projects using additive scenes (e.g., GameBrain + GameWorld):
Scene Structure:
GameBrain (Manager Scene)
├── ObjectPoolManager
│ ├── AudioSourcePool_Parent
│ │ └── AudioSource_Pool0 (lives in GameBrain)
│ └── BulletPool_Parent
│ └── Bullet_Pool0 (lives in GameBrain)
GameWorld (Content Scene)
└── Level geometry, environment
Key Features:
- Pooled objects are explicitly moved to ObjectPoolManager's scene (GameBrain)
- Pools persist when GameWorld unloads/reloads
- No cross-scene parenting issues
- Clean separation: managers in GameBrain, pools in GameBrain, content in GameWorld
How It Works:
- ObjectPoolManager lives in GameBrain scene
- When creating pooled objects, system calls
SceneManager.MoveGameObjectToScene() - All pooled objects move to GameBrain scene automatically
- Parents are created as children of ObjectPoolManager in GameBrain
This ensures pools survive level transitions and scene reloads.
Pool exhausted warnings:
- Increase
initialSizeormaxGrowthSize - Enable
allowGrowth - Check if objects are being returned properly
Null references when getting objects:
- Verify prefab address/path is correct
- Check Addressables build includes the asset
- Ensure PoolDatabase is assigned to ObjectPoolManager
Objects not resetting properly:
- Implement
IPoolableinterface - Override
ReturnToPool()to reset custom state
// Generic get/return
T Get<T>(string key) where T : class
void Return<T>(string key, T item) where T : class
// VFX convenience (auto-return after duration)
ParticleSystem GetVFX(string key, Vector3 position, Quaternion rotation)
// SFX convenience (auto-return after delay)
AudioSource PlaySFX(string key, AudioClip clip, float volume = 1f, float delayReturn = 2f)GameObject PoolableObject { get; }
void GetFromPool(); // Called when retrieved from pool
void ReturnToPool(); // Called when returned to pool