using UnityEngine;
using System.Collections.Generic;
///
/// ObjectPool — Generic, lightweight object pool for mobile performance.
/// Eliminates Instantiate/Destroy overhead for frequently spawned objects
/// (VFX, money bags, projectiles, UI elements).
///
/// Usage:
/// ObjectPool pool = new ObjectPool(prefab, initialSize: 10);
/// GameObject obj = pool.Get(position, rotation);
/// pool.Return(obj); // or obj.GetComponent().ReturnToPool();
///
public class ObjectPool
{
private readonly GameObject _prefab;
private readonly Transform _container;
private readonly Queue _available;
private readonly HashSet _inUse;
private readonly int _maxSize;
private readonly bool _allowGrowth;
public int AvailableCount => _available.Count;
public int InUseCount => _inUse.Count;
public int TotalCount => _available.Count + _inUse.Count;
///
/// Create a new object pool.
///
/// Prefab to pool
/// Pre-warm count
/// Hard cap (0 = unlimited)
/// Optional parent transform for organization
/// Allow creating beyond initialSize if pool is empty
public ObjectPool(GameObject prefab, int initialSize = 10, int maxSize = 0, Transform parent = null, bool allowGrowth = true)
{
_prefab = prefab;
_maxSize = maxSize;
_allowGrowth = allowGrowth;
_available = new Queue(initialSize);
_inUse = new HashSet();
// Create container for organization
if (parent == null)
{
GameObject containerObj = new GameObject($"Pool_{prefab.name}");
_container = containerObj.transform;
Object.DontDestroyOnLoad(containerObj);
}
else
{
_container = parent;
}
// Pre-warm
for (int i = 0; i < initialSize; i++)
{
GameObject obj = CreateNewInstance();
obj.SetActive(false);
_available.Enqueue(obj);
}
}
///
/// Get an object from the pool. Activates it at the given position/rotation.
///
public GameObject Get(Vector3 position, Quaternion rotation)
{
GameObject obj = null;
// Try to get from pool
while (_available.Count > 0)
{
obj = _available.Dequeue();
if (obj != null) break; // Found valid object
obj = null; // Was destroyed externally, skip
}
// Pool empty — create new if allowed
if (obj == null)
{
if (!_allowGrowth && _maxSize > 0 && TotalCount >= _maxSize)
return null; // Hard cap reached
obj = CreateNewInstance();
}
obj.transform.SetPositionAndRotation(position, rotation);
obj.SetActive(true);
_inUse.Add(obj);
// Notify pooled components
var pooled = obj.GetComponent();
if (pooled != null) pooled.OnGetFromPool();
return obj;
}
///
/// Return an object to the pool. Deactivates it.
///
public void Return(GameObject obj)
{
if (obj == null) return;
_inUse.Remove(obj);
// Notify pooled components
var pooled = obj.GetComponent();
if (pooled != null) pooled.OnReturnToPool();
obj.SetActive(false);
obj.transform.SetParent(_container);
_available.Enqueue(obj);
}
///
/// Return all in-use objects to the pool.
///
public void ReturnAll()
{
// Copy to avoid modification during iteration
var inUseList = new List(_inUse);
foreach (var obj in inUseList)
{
if (obj != null) Return(obj);
}
}
///
/// Destroy all pooled objects and clear the pool.
///
public void Dispose()
{
foreach (var obj in _available)
if (obj != null) Object.Destroy(obj);
foreach (var obj in _inUse)
if (obj != null) Object.Destroy(obj);
_available.Clear();
_inUse.Clear();
if (_container != null)
Object.Destroy(_container.gameObject);
}
private GameObject CreateNewInstance()
{
GameObject obj = Object.Instantiate(_prefab, _container);
obj.name = _prefab.name; // Remove "(Clone)" suffix
// Add PooledObject component if not present
var pooled = obj.GetComponent();
if (pooled == null)
{
pooled = obj.AddComponent();
}
pooled.OwnerPool = this;
return obj;
}
}
///
/// PooledObject — Attach to pooled prefabs. Provides auto-return and lifecycle callbacks.
///
public class PooledObject : MonoBehaviour
{
public ObjectPool OwnerPool { get; set; }
[Tooltip("Auto-return to pool after this many seconds (0 = manual return only)")]
[SerializeField] private float autoReturnDelay = 0f;
private float _activeTimer;
private Coroutine _autoReturnCoroutine;
/// Called when taken from pool.
public virtual void OnGetFromPool()
{
_activeTimer = 0f;
// Start coroutine-based auto-return instead of per-frame Update check
if (autoReturnDelay > 0f)
{
if (_autoReturnCoroutine != null) StopCoroutine(_autoReturnCoroutine);
_autoReturnCoroutine = StartCoroutine(AutoReturnAfterDelay());
}
}
/// Called when returned to pool.
public virtual void OnReturnToPool()
{
_activeTimer = 0f;
if (_autoReturnCoroutine != null)
{
StopCoroutine(_autoReturnCoroutine);
_autoReturnCoroutine = null;
}
}
/// Return this object to its pool.
public void ReturnToPool()
{
if (_autoReturnCoroutine != null)
{
StopCoroutine(_autoReturnCoroutine);
_autoReturnCoroutine = null;
}
if (OwnerPool != null)
OwnerPool.Return(gameObject);
else
gameObject.SetActive(false);
}
/// Coroutine-based auto-return — eliminates per-frame Update overhead.
private System.Collections.IEnumerator AutoReturnAfterDelay()
{
yield return new WaitForSeconds(autoReturnDelay);
ReturnToPool();
}
}
///
/// PoolManager — Centralized pool registry. Access pools by prefab reference.
/// Singleton, persists across scenes.
///
public class PoolManager : MonoBehaviour
{
private static PoolManager _instance;
public static PoolManager Instance
{
get
{
if (_instance == null)
{
var go = new GameObject("PoolManager");
_instance = go.AddComponent();
DontDestroyOnLoad(go);
}
return _instance;
}
}
private readonly Dictionary _pools = new Dictionary();
///
/// Get or create a pool for a prefab.
///
public ObjectPool GetPool(GameObject prefab, int initialSize = 10, int maxSize = 0)
{
if (_pools.TryGetValue(prefab, out ObjectPool pool))
return pool;
pool = new ObjectPool(prefab, initialSize, maxSize, transform);
_pools[prefab] = pool;
return pool;
}
///
/// Shorthand: Get an object from a prefab's pool.
///
public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation, int poolSize = 10)
{
return GetPool(prefab, poolSize).Get(position, rotation);
}
///
/// Shorthand: Return an object to its pool.
///
public void Despawn(GameObject obj)
{
var pooled = obj.GetComponent();
if (pooled != null && pooled.OwnerPool != null)
pooled.OwnerPool.Return(obj);
else
obj.SetActive(false);
}
///
/// Pre-warm a pool for a prefab.
///
public void PreWarm(GameObject prefab, int count)
{
GetPool(prefab, count);
}
///
/// Clear all pools (call on scene transitions).
///
public void ClearAll()
{
foreach (var pool in _pools.Values)
pool.Dispose();
_pools.Clear();
}
private void OnDestroy()
{
ClearAll();
_instance = null;
}
}