# Tools Unity Object Pooling

> Object pooling patterns for Unity to reduce GC allocations and improve performance.

- **Type:** Skill
- **Install:** `agentstack add skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-object-pooling`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [tjboudreaux](https://agentstack.voostack.com/s/tjboudreaux)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [tjboudreaux](https://github.com/tjboudreaux)
- **Source:** https://github.com/tjboudreaux/cc-plugin-unity-gamedev/tree/main/skills/tools-unity-object-pooling

## Install

```sh
agentstack add skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-object-pooling
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Unity Object Pooling

## Overview

Object pooling reuses GameObjects instead of instantiating/destroying them, reducing GC pressure and improving performance.

## When to Use

- Frequently spawned objects (projectiles, particles, enemies)
- UI list items
- Audio sources
- Network messages
- Any high-frequency instantiation

## Basic Object Pool

### Generic Pool Implementation

```csharp
public class ObjectPool where T : class
{
    private readonly Stack _pool = new();
    private readonly Func _createFunc;
    private readonly Action _onGet;
    private readonly Action _onRelease;
    private readonly Action _onDestroy;
    private readonly int _maxSize;
    
    public int CountActive { get; private set; }
    public int CountInactive => _pool.Count;
    public int CountAll => CountActive + CountInactive;
    
    public ObjectPool(
        Func createFunc,
        Action onGet = null,
        Action onRelease = null,
        Action onDestroy = null,
        int defaultCapacity = 10,
        int maxSize = 1000)
    {
        _createFunc = createFunc ?? throw new ArgumentNullException(nameof(createFunc));
        _onGet = onGet;
        _onRelease = onRelease;
        _onDestroy = onDestroy;
        _maxSize = maxSize;
        
        // Pre-warm pool
        for (int i = 0; i  0)
        {
            obj = _pool.Pop();
        }
        else
        {
            obj = _createFunc();
        }
        
        _onGet?.Invoke(obj);
        CountActive++;
        return obj;
    }
    
    public void Release(T obj)
    {
        if (obj == null) return;
        
        _onRelease?.Invoke(obj);
        CountActive--;
        
        if (_pool.Count  0)
        {
            var obj = _pool.Pop();
            _onDestroy?.Invoke(obj);
        }
        CountActive = 0;
    }
}
```

### Unity Built-in Pool (2021+)

```csharp
using UnityEngine.Pool;

public class ProjectileSpawner : MonoBehaviour
{
    [SerializeField] private Projectile _prefab;
    
    private ObjectPool _pool;
    
    private void Awake()
    {
        _pool = new ObjectPool(
            createFunc: CreateProjectile,
            actionOnGet: OnGetProjectile,
            actionOnRelease: OnReleaseProjectile,
            actionOnDestroy: OnDestroyProjectile,
            collectionCheck: true,
            defaultCapacity: 20,
            maxSize: 100
        );
    }
    
    private Projectile CreateProjectile()
    {
        var proj = Instantiate(_prefab);
        proj.SetPool(_pool);
        return proj;
    }
    
    private void OnGetProjectile(Projectile proj)
    {
        proj.gameObject.SetActive(true);
    }
    
    private void OnReleaseProjectile(Projectile proj)
    {
        proj.gameObject.SetActive(false);
    }
    
    private void OnDestroyProjectile(Projectile proj)
    {
        Destroy(proj.gameObject);
    }
    
    public Projectile Spawn(Vector3 position, Quaternion rotation)
    {
        var proj = _pool.Get();
        proj.transform.SetPositionAndRotation(position, rotation);
        return proj;
    }
}
```

### Pooled Object Base Class

```csharp
public abstract class PooledObject : MonoBehaviour where T : PooledObject
{
    private IObjectPool _pool;
    
    public void SetPool(IObjectPool pool)
    {
        _pool = pool;
    }
    
    public void ReturnToPool()
    {
        if (_pool != null)
        {
            _pool.Release((T)this);
        }
        else
        {
            Destroy(gameObject);
        }
    }
    
    public virtual void OnSpawn() { }
    public virtual void OnDespawn() { }
}

// Usage
public class Projectile : PooledObject
{
    [SerializeField] private float _lifetime = 5f;
    private float _spawnTime;
    
    public override void OnSpawn()
    {
        _spawnTime = Time.time;
    }
    
    public override void OnDespawn()
    {
        // Reset state
    }
    
    private void Update()
    {
        if (Time.time - _spawnTime > _lifetime)
        {
            ReturnToPool();
        }
    }
}
```

## Multi-Prefab Pool

### Pool Manager

```csharp
public class PoolManager : MonoBehaviour
{
    public static PoolManager Instance { get; private set; }
    
    private readonly Dictionary> _pools = new();
    
    private void Awake()
    {
        Instance = this;
    }
    
    public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation)
    {
        if (!_pools.TryGetValue(prefab, out var pool))
        {
            pool = CreatePool(prefab);
            _pools[prefab] = pool;
        }
        
        var obj = pool.Get();
        obj.transform.SetPositionAndRotation(position, rotation);
        return obj;
    }
    
    public void Despawn(GameObject obj, GameObject prefab)
    {
        if (_pools.TryGetValue(prefab, out var pool))
        {
            pool.Release(obj);
        }
        else
        {
            Destroy(obj);
        }
    }
    
    public void PrewarmPool(GameObject prefab, int count)
    {
        if (!_pools.ContainsKey(prefab))
        {
            var pool = CreatePool(prefab, count);
            _pools[prefab] = pool;
        }
    }
    
    private ObjectPool CreatePool(GameObject prefab, int initialSize = 10)
    {
        Transform poolParent = new GameObject($"Pool_{prefab.name}").transform;
        poolParent.SetParent(transform);
        
        return new ObjectPool(
            createFunc: () =>
            {
                var obj = Instantiate(prefab, poolParent);
                obj.SetActive(false);
                return obj;
            },
            actionOnGet: obj => obj.SetActive(true),
            actionOnRelease: obj => obj.SetActive(false),
            actionOnDestroy: obj => Destroy(obj),
            defaultCapacity: initialSize,
            maxSize: 200
        );
    }
}
```

### Pooled Spawn Extension

```csharp
public static class PoolExtensions
{
    public static GameObject SpawnPooled(
        this GameObject prefab, 
        Vector3 position, 
        Quaternion rotation)
    {
        return PoolManager.Instance.Spawn(prefab, position, rotation);
    }
    
    public static void DespawnPooled(this GameObject obj, GameObject prefab)
    {
        PoolManager.Instance.Despawn(obj, prefab);
    }
}

// Usage
var enemy = _enemyPrefab.SpawnPooled(spawnPoint, Quaternion.identity);
// Later...
enemy.DespawnPooled(_enemyPrefab);
```

## Component Pool

### Component-Specific Pool

```csharp
public class ComponentPool where T : Component
{
    private readonly ObjectPool _pool;
    private readonly Transform _parent;
    
    public ComponentPool(T prefab, Transform parent, int initialSize = 10)
    {
        _parent = parent;
        
        _pool = new ObjectPool(
            createFunc: () =>
            {
                var obj = UnityEngine.Object.Instantiate(prefab, parent);
                obj.gameObject.SetActive(false);
                return obj;
            },
            actionOnGet: c => c.gameObject.SetActive(true),
            actionOnRelease: c =>
            {
                c.gameObject.SetActive(false);
                c.transform.SetParent(_parent);
            },
            actionOnDestroy: c => UnityEngine.Object.Destroy(c.gameObject),
            defaultCapacity: initialSize
        );
    }
    
    public T Get() => _pool.Get();
    public void Release(T component) => _pool.Release(component);
}

// Usage for UI
public class DamageNumberPool : MonoBehaviour
{
    [SerializeField] private DamageNumber _prefab;
    
    private ComponentPool _pool;
    
    private void Awake()
    {
        _pool = new ComponentPool(_prefab, transform, 20);
    }
    
    public DamageNumber Show(Vector3 worldPos, int damage)
    {
        var number = _pool.Get();
        number.Initialize(worldPos, damage, () => _pool.Release(number));
        return number;
    }
}
```

## List/Collection Pooling

### List Pool

```csharp
public static class ListPool
{
    private static readonly ObjectPool> s_Pool = new(
        createFunc: () => new List(),
        actionOnRelease: list => list.Clear(),
        defaultCapacity: 10,
        maxSize: 100
    );
    
    public static List Get() => s_Pool.Get();
    
    public static void Release(List list)
    {
        if (list != null)
            s_Pool.Release(list);
    }
    
    // Disposable wrapper
    public static PooledList GetDisposable()
    {
        return new PooledList(s_Pool.Get());
    }
}

public struct PooledList : IDisposable
{
    public List List { get; }
    
    public PooledList(List list)
    {
        List = list;
    }
    
    public void Dispose()
    {
        ListPool.Release(List);
    }
}

// Usage
using (var pooledList = ListPool.GetDisposable())
{
    GetEnemiesInRange(pooledList.List);
    foreach (var enemy in pooledList.List)
    {
        // Process
    }
} // Automatically returned to pool
```

### StringBuilder Pool

```csharp
public static class StringBuilderPool
{
    private static readonly ObjectPool s_Pool = new(
        createFunc: () => new StringBuilder(256),
        actionOnRelease: sb => sb.Clear(),
        defaultCapacity: 5,
        maxSize: 50
    );
    
    public static StringBuilder Get() => s_Pool.Get();
    public static void Release(StringBuilder sb) => s_Pool.Release(sb);
    
    public static string GetStringAndRelease(StringBuilder sb)
    {
        string result = sb.ToString();
        Release(sb);
        return result;
    }
}

// Usage
var sb = StringBuilderPool.Get();
sb.Append("Player: ");
sb.Append(playerName);
sb.Append(" Score: ");
sb.Append(score);
string message = StringBuilderPool.GetStringAndRelease(sb);
```

## Audio Pool

### Pooled Audio Source

```csharp
public class AudioPool : MonoBehaviour
{
    [SerializeField] private AudioSource _prefab;
    [SerializeField] private int _poolSize = 20;
    
    private ObjectPool _pool;
    
    private void Awake()
    {
        _pool = new ObjectPool(
            createFunc: () =>
            {
                var source = Instantiate(_prefab, transform);
                source.gameObject.SetActive(false);
                return source;
            },
            actionOnGet: source => source.gameObject.SetActive(true),
            actionOnRelease: source =>
            {
                source.Stop();
                source.clip = null;
                source.gameObject.SetActive(false);
            },
            defaultCapacity: _poolSize,
            maxSize: _poolSize * 2
        );
    }
    
    public void PlayOneShot(AudioClip clip, Vector3 position, float volume = 1f)
    {
        var source = _pool.Get();
        source.transform.position = position;
        source.clip = clip;
        source.volume = volume;
        source.Play();
        
        // Return after clip finishes
        StartCoroutine(ReturnAfterPlay(source, clip.length));
    }
    
    private IEnumerator ReturnAfterPlay(AudioSource source, float delay)
    {
        yield return new WaitForSeconds(delay + 0.1f);
        _pool.Release(source);
    }
}
```

## Performance Considerations

### Warm-up Strategy

```csharp
public class PoolWarmer : MonoBehaviour
{
    [SerializeField] private PoolWarmupConfig[] _configs;
    
    private async UniTaskVoid Start()
    {
        await WarmPools();
    }
    
    private async UniTask WarmPools()
    {
        foreach (var config in _configs)
        {
            PoolManager.Instance.PrewarmPool(config.Prefab, config.InitialCount);
            
            // Spread over frames
            if (config.InitialCount > 10)
            {
                await UniTask.Yield();
            }
        }
    }
}

[Serializable]
public class PoolWarmupConfig
{
    public GameObject Prefab;
    public int InitialCount = 10;
}
```

### Pool Statistics

```csharp
public class PoolStats
{
    private readonly Dictionary _metrics = new();
    
    public void RecordGet(string poolName)
    {
        GetOrCreate(poolName).Gets++;
    }
    
    public void RecordRelease(string poolName)
    {
        GetOrCreate(poolName).Releases++;
    }
    
    public void RecordCreate(string poolName)
    {
        GetOrCreate(poolName).Creates++;
    }
    
    public void LogStats()
    {
        foreach (var (name, metrics) in _metrics)
        {
            float hitRate = metrics.Gets > 0 
                ? (float)(metrics.Gets - metrics.Creates) / metrics.Gets 
                : 0;
            
            Debug.Log($"Pool {name}: Gets={metrics.Gets}, Creates={metrics.Creates}, HitRate={hitRate:P1}");
        }
    }
    
    private PoolMetrics GetOrCreate(string name)
    {
        if (!_metrics.TryGetValue(name, out var metrics))
        {
            metrics = new PoolMetrics();
            _metrics[name] = metrics;
        }
        return metrics;
    }
    
    private class PoolMetrics
    {
        public int Gets;
        public int Releases;
        public int Creates;
    }
}
```

## Best Practices

1. **Pre-warm pools** during loading screens
2. **Set appropriate max sizes** - Don't pool forever
3. **Reset state** on release, not on get
4. **Use composition** - PooledObject base class
5. **Track pool hit rates** - Optimize sizes
6. **Release in OnDisable** - Handle scene changes
7. **Parent inactive objects** - Organize hierarchy
8. **Use built-in pools** when available (2021+)
9. **Pool expensive components** - Particle systems, audio
10. **Profile allocation reduction** - Verify GC improvement

## Troubleshooting

| Issue | Solution |
|-------|----------|
| Objects not resetting | Implement OnDespawn/Reset |
| Pool growing indefinitely | Set maxSize limit |
| Objects active when spawned | SetActive in create, not get |
| Memory not decreasing | Clear pools on scene change |
| Wrong pool used | Track prefab->pool mapping |

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [tjboudreaux](https://github.com/tjboudreaux)
- **Source:** [tjboudreaux/cc-plugin-unity-gamedev](https://github.com/tjboudreaux/cc-plugin-unity-gamedev)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** yes
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-object-pooling
- Seller: https://agentstack.voostack.com/s/tjboudreaux
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
