using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
///
/// Centralized manager for GameObject pooling using Unity's built-in ObjectPool system.
/// Handles per-prefab pools for projectiles, AOE effects, and other frequently instantiated objects.
///
/// USAGE:
/// - Replace Object.Instantiate(prefab, pos, rot) with GameObjectPoolManager.Instance.Get(prefab, pos, rot)
/// - Replace Destroy(gameObject) with GameObjectPoolManager.Instance.Release(gameObject)
///
/// TO ADD NEW PREFAB TYPES:
/// 1. Add prefab to poolConfigs list in inspector or call RegisterPrefab() at runtime
/// 2. That's it! The system automatically creates pools for any prefab you Get()
///
public class GameObjectPoolManager : MonoBehaviour
{
[System.Serializable]
public class PoolConfig
{
[Tooltip("The prefab to pool")]
public GameObject prefab;
[Tooltip("Initial number of objects to create in pool")]
public int defaultCapacity = 50;
[Tooltip("Maximum objects in pool before oldest gets destroyed")]
public int maxSize = 100;
[Tooltip("Check objects when returned to pool")]
public bool collectionCheck = true;
}
#region Singleton
private static GameObjectPoolManager _instance;
public static GameObjectPoolManager Instance
{
get
{
if (_instance == null)
{
_instance = FindFirstObjectByType();
if (_instance == null)
{
GameObject go = new GameObject("GameObjectPoolManager");
_instance = go.AddComponent();
DontDestroyOnLoad(go);
}
}
return _instance;
}
}
private void Awake()
{
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(gameObject);
InitializePools();
}
else if (_instance != this)
{
Destroy(gameObject);
}
}
#endregion
[Header("Pool Configuration")]
[SerializeField] private List poolConfigs = new List();
[ContextMenu("Reset All Pool Configs")]
private void ResetPoolConfigs()
{
foreach (var config in poolConfigs)
{
if (config.prefab != null)
{
config.defaultCapacity = 50;
config.maxSize = 100;
config.collectionCheck = true;
Debug.Log($"Reset config for {config.prefab.name}");
}
}
Debug.Log("All pool configs reset to default values");
}
[Header("Debug Info")]
[SerializeField] private bool showDebugLogs = true; // Temporarily enabled for debugging
[SerializeField] private bool showPoolStats = false;
// Runtime pool storage - maps prefab to its ObjectPool
private Dictionary> pools = new Dictionary>();
// Track which pool each active object belongs to for release
private Dictionary activeObjectToPrefab = new Dictionary();
#region Public API
///
/// Get a pooled GameObject instance. Creates pool if it doesn't exist.
///
/// Prefab to instantiate
/// World position
/// World rotation
/// Pooled GameObject instance
public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation)
{
if (prefab == null)
{
Debug.LogError("GameObjectPoolManager: Cannot get null prefab");
return null;
}
// Ensure pool exists
if (!pools.ContainsKey(prefab))
{
CreatePoolForPrefab(prefab);
}
// Get object from pool
GameObject obj = pools[prefab].Get();
// Set position and rotation
obj.transform.position = position;
obj.transform.rotation = rotation;
// Track this object
activeObjectToPrefab[obj] = prefab;
if (showDebugLogs)
Debug.Log($"GameObjectPoolManager: Got {prefab.name} from pool");
return obj;
}
///
/// Return a GameObject to its pool.
///
/// Object to return to pool
public void Release(GameObject obj)
{
if (obj == null)
{
Debug.LogError("GameObjectPoolManager: Cannot release null object");
return;
}
// Find which prefab this object belongs to
if (!activeObjectToPrefab.TryGetValue(obj, out GameObject prefab))
{
Debug.LogWarning($"GameObjectPoolManager: Object {obj.name} not tracked. This might be a non-pooled object. Destroying instead.");
Destroy(obj);
return;
}
// Remove from tracking
activeObjectToPrefab.Remove(obj);
// Return to pool
pools[prefab].Release(obj);
if (showDebugLogs)
Debug.Log($"GameObjectPoolManager: Released {obj.name} to {prefab.name} pool");
}
///
/// Register a new prefab for pooling at runtime.
///
/// Prefab to register
/// Initial pool size
/// Maximum pool size
public void RegisterPrefab(GameObject prefab, int defaultCapacity = 50, int maxSize = 100)
{
if (prefab == null) return;
if (pools.ContainsKey(prefab))
{
Debug.LogWarning($"GameObjectPoolManager: Prefab {prefab.name} already registered");
return;
}
PoolConfig config = new PoolConfig
{
prefab = prefab,
defaultCapacity = defaultCapacity,
maxSize = maxSize,
collectionCheck = true
};
poolConfigs.Add(config);
CreatePool(config);
Debug.Log($"GameObjectPoolManager: Registered and created pool for {prefab.name}");
}
#endregion
#region Pool Management
private void InitializePools()
{
foreach (var config in poolConfigs)
{
if (config.prefab != null && ValidatePoolConfig(config))
{
CreatePool(config);
}
}
Debug.Log($"GameObjectPoolManager: Initialized {pools.Count} pools");
}
private bool ValidatePoolConfig(PoolConfig config)
{
if (config.defaultCapacity <= 0)
{
Debug.LogError($"GameObjectPoolManager: Invalid defaultCapacity ({config.defaultCapacity}) for {config.prefab.name}. Must be > 0. Using default value 50.");
config.defaultCapacity = 50;
}
if (config.maxSize <= 0)
{
Debug.LogError($"GameObjectPoolManager: Invalid maxSize ({config.maxSize}) for {config.prefab.name}. Must be > 0. Using default value 100.");
config.maxSize = 100;
}
if (config.maxSize < config.defaultCapacity)
{
Debug.LogWarning($"GameObjectPoolManager: maxSize ({config.maxSize}) is less than defaultCapacity ({config.defaultCapacity}) for {config.prefab.name}. Setting maxSize to {config.defaultCapacity}.");
config.maxSize = config.defaultCapacity;
}
return true;
}
private void CreatePoolForPrefab(GameObject prefab)
{
// Check if we have a config for this prefab
PoolConfig config = poolConfigs.Find(c => c.prefab == prefab);
if (config == null)
{
// Create default config
config = new PoolConfig
{
prefab = prefab,
defaultCapacity = 50,
maxSize = 100,
collectionCheck = true
};
Debug.Log($"GameObjectPoolManager: Auto-creating pool for {prefab.name} with default settings");
}
// Always validate config before creating pool
ValidatePoolConfig(config);
CreatePool(config);
}
private void CreatePool(PoolConfig config)
{
if (pools.ContainsKey(config.prefab))
{
Debug.LogWarning($"GameObjectPoolManager: Pool for {config.prefab.name} already exists");
return;
}
Debug.Log($"GameObjectPoolManager: Creating pool for {config.prefab.name} with defaultCapacity={config.defaultCapacity}, maxSize={config.maxSize}");
var pool = new ObjectPool(
createFunc: () => CreatePooledObject(config.prefab),
actionOnGet: (obj) => OnGetFromPool(obj),
actionOnRelease: (obj) => OnReleaseToPool(obj),
actionOnDestroy: (obj) => OnDestroyPooledObject(obj),
collectionCheck: config.collectionCheck,
defaultCapacity: config.defaultCapacity,
maxSize: config.maxSize
);
pools[config.prefab] = pool;
Debug.Log($"GameObjectPoolManager: Successfully created pool for {config.prefab.name}");
}
#endregion
#region Pool Callbacks
private GameObject CreatePooledObject(GameObject prefab)
{
GameObject obj = Instantiate(prefab);
obj.name = prefab.name + "(Pooled)";
// Add PooledObject component to track pool membership
var pooledComponent = obj.GetComponent();
if (pooledComponent == null)
{
pooledComponent = obj.AddComponent();
}
pooledComponent.Initialize(prefab);
if (showDebugLogs)
Debug.Log($"GameObjectPoolManager: Created new pooled object {obj.name}");
return obj;
}
private void OnGetFromPool(GameObject obj)
{
// Reset any IPoolable components FIRST
var poolables = obj.GetComponents();
for (int i = 0; i < poolables.Length; i++)
{
poolables[i].OnGetFromPool();
}
// Activate object AFTER reset
obj.SetActive(true);
}
private void OnReleaseToPool(GameObject obj)
{
// Reset any IPoolable components
var poolables = obj.GetComponents();
for (int i = 0; i < poolables.Length; i++)
{
poolables[i].OnReleaseToPool();
}
obj.SetActive(false);
}
private void OnDestroyPooledObject(GameObject obj)
{
if (obj != null)
{
Destroy(obj);
}
}
#endregion
#region Debug and Stats
private void OnGUI()
{
if (!showPoolStats) return;
GUILayout.BeginArea(new Rect(10, 10, 300, 200));
GUILayout.Label("Pool Stats", GUI.skin.box);
foreach (var kvp in pools)
{
string prefabName = kvp.Key.name;
int activeCount = activeObjectToPrefab.Values.Count;
GUILayout.Label($"{prefabName}: {activeCount} active");
}
GUILayout.EndArea();
}
#endregion
}
///
/// Interface for objects that need custom reset behavior when pooled/released.
/// Implement this on components that need to reset state when returned to pool.
///
public interface IPoolable
{
void OnGetFromPool();
void OnReleaseToPool();
}
///
/// Component automatically added to pooled objects to track their source prefab.
///
public class PooledObject : MonoBehaviour
{
[SerializeField] private GameObject sourcePrefab;
public GameObject SourcePrefab => sourcePrefab;
public void Initialize(GameObject prefab)
{
sourcePrefab = prefab;
}
}