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; } }