chunk 1: core gameplay scripts scenes runtime assets
This commit is contained in:
302
Assets/Scripts/Core/ObjectPool.cs
Normal file
302
Assets/Scripts/Core/ObjectPool.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// 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<PooledObject>().ReturnToPool();
|
||||
/// </summary>
|
||||
public class ObjectPool
|
||||
{
|
||||
private readonly GameObject _prefab;
|
||||
private readonly Transform _container;
|
||||
private readonly Queue<GameObject> _available;
|
||||
private readonly HashSet<GameObject> _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;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new object pool.
|
||||
/// </summary>
|
||||
/// <param name="prefab">Prefab to pool</param>
|
||||
/// <param name="initialSize">Pre-warm count</param>
|
||||
/// <param name="maxSize">Hard cap (0 = unlimited)</param>
|
||||
/// <param name="parent">Optional parent transform for organization</param>
|
||||
/// <param name="allowGrowth">Allow creating beyond initialSize if pool is empty</param>
|
||||
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<GameObject>(initialSize);
|
||||
_inUse = new HashSet<GameObject>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an object from the pool. Activates it at the given position/rotation.
|
||||
/// </summary>
|
||||
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<PooledObject>();
|
||||
if (pooled != null) pooled.OnGetFromPool();
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return an object to the pool. Deactivates it.
|
||||
/// </summary>
|
||||
public void Return(GameObject obj)
|
||||
{
|
||||
if (obj == null) return;
|
||||
|
||||
_inUse.Remove(obj);
|
||||
|
||||
// Notify pooled components
|
||||
var pooled = obj.GetComponent<PooledObject>();
|
||||
if (pooled != null) pooled.OnReturnToPool();
|
||||
|
||||
obj.SetActive(false);
|
||||
obj.transform.SetParent(_container);
|
||||
_available.Enqueue(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return all in-use objects to the pool.
|
||||
/// </summary>
|
||||
public void ReturnAll()
|
||||
{
|
||||
// Copy to avoid modification during iteration
|
||||
var inUseList = new List<GameObject>(_inUse);
|
||||
foreach (var obj in inUseList)
|
||||
{
|
||||
if (obj != null) Return(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy all pooled objects and clear the pool.
|
||||
/// </summary>
|
||||
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<PooledObject>();
|
||||
if (pooled == null)
|
||||
{
|
||||
pooled = obj.AddComponent<PooledObject>();
|
||||
}
|
||||
pooled.OwnerPool = this;
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PooledObject — Attach to pooled prefabs. Provides auto-return and lifecycle callbacks.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
/// <summary>Called when taken from pool.</summary>
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Called when returned to pool.</summary>
|
||||
public virtual void OnReturnToPool()
|
||||
{
|
||||
_activeTimer = 0f;
|
||||
if (_autoReturnCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_autoReturnCoroutine);
|
||||
_autoReturnCoroutine = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Return this object to its pool.</summary>
|
||||
public void ReturnToPool()
|
||||
{
|
||||
if (_autoReturnCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_autoReturnCoroutine);
|
||||
_autoReturnCoroutine = null;
|
||||
}
|
||||
if (OwnerPool != null)
|
||||
OwnerPool.Return(gameObject);
|
||||
else
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>Coroutine-based auto-return — eliminates per-frame Update overhead.</summary>
|
||||
private System.Collections.IEnumerator AutoReturnAfterDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(autoReturnDelay);
|
||||
ReturnToPool();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PoolManager — Centralized pool registry. Access pools by prefab reference.
|
||||
/// Singleton, persists across scenes.
|
||||
/// </summary>
|
||||
public class PoolManager : MonoBehaviour
|
||||
{
|
||||
private static PoolManager _instance;
|
||||
public static PoolManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
var go = new GameObject("PoolManager");
|
||||
_instance = go.AddComponent<PoolManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<GameObject, ObjectPool> _pools = new Dictionary<GameObject, ObjectPool>();
|
||||
|
||||
/// <summary>
|
||||
/// Get or create a pool for a prefab.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shorthand: Get an object from a prefab's pool.
|
||||
/// </summary>
|
||||
public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation, int poolSize = 10)
|
||||
{
|
||||
return GetPool(prefab, poolSize).Get(position, rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shorthand: Return an object to its pool.
|
||||
/// </summary>
|
||||
public void Despawn(GameObject obj)
|
||||
{
|
||||
var pooled = obj.GetComponent<PooledObject>();
|
||||
if (pooled != null && pooled.OwnerPool != null)
|
||||
pooled.OwnerPool.Return(obj);
|
||||
else
|
||||
obj.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pre-warm a pool for a prefab.
|
||||
/// </summary>
|
||||
public void PreWarm(GameObject prefab, int count)
|
||||
{
|
||||
GetPool(prefab, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all pools (call on scene transitions).
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
foreach (var pool in _pools.Values)
|
||||
pool.Dispose();
|
||||
_pools.Clear();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ClearAll();
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user