using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Pool; /// /// Centralized prefab pooling manager for reusable GameObject instances. /// /// namespace BrewMonster.Scripts { public class PrefabPoolManager : MonoBehaviour { private static PrefabPoolManager instance; private readonly Dictionary poolsByPrefab = new(); private readonly Dictionary allInstances = new(); private readonly Dictionary activeInstances = new(); public static PrefabPoolManager Instance { get { if (instance == null) { instance = FindAnyObjectByType(); if (instance == null) { var managerObject = new GameObject(nameof(PrefabPoolManager)); instance = managerObject.AddComponent(); } } return instance; } } public void InitPool(GameObject prefab, int defaultCapacity = 10, int maxSize = 50) { if (prefab == null) { Debug.LogError("[PrefabPoolManager] Cannot initialize a pool with a null prefab."); return; } if (poolsByPrefab.ContainsKey(prefab)) return; defaultCapacity = Mathf.Max(0, defaultCapacity); maxSize = Mathf.Max(1, maxSize); if (defaultCapacity > maxSize) defaultCapacity = maxSize; var record = new PoolRecord { Prefab = prefab, Container = CreatePoolContainer(prefab) }; record.Pool = new ObjectPool( () => CreateInstance(record), obj => OnGetFromPool(record, obj), obj => OnReleaseToPool(record, obj), OnDestroyPooledObject, collectionCheck: Application.isEditor, defaultCapacity: defaultCapacity, maxSize: maxSize); poolsByPrefab.Add(prefab, record); Prewarm(record, defaultCapacity); } public GameObject Spawn(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent = null) { if (prefab == null) { Debug.LogError("[PrefabPoolManager] Cannot spawn a null prefab."); return null; } if (!poolsByPrefab.TryGetValue(prefab, out PoolRecord record)) { InitPool(prefab); record = poolsByPrefab[prefab]; } GameObject instanceObject = record.Pool.Get(); instanceObject.transform.SetPositionAndRotation(position, rotation); if (parent != null) { instanceObject.transform.SetParent(parent); } return instanceObject; } public void Despawn(GameObject obj) { if (obj == null) return; if (!activeInstances.TryGetValue(obj, out PoolRecord record)) { if (allInstances.ContainsKey(obj)) { Debug.LogWarning($"[PrefabPoolManager] Object '{obj.name}' has already been returned to the pool."); return; } Debug.LogWarning($"[PrefabPoolManager] Object '{obj.name}' was not spawned by PrefabPoolManager. Destroying it instead."); Destroy(obj); return; } record.Pool.Release(obj); } private void Awake() { if (instance != null && instance != this) { Destroy(gameObject); return; } instance = this; //DontDestroyOnLoad(gameObject); } private void OnDestroy() { if (instance != this) return; foreach (PoolRecord record in poolsByPrefab.Values) { record.Pool.Clear(); } poolsByPrefab.Clear(); allInstances.Clear(); activeInstances.Clear(); instance = null; } private Transform CreatePoolContainer(GameObject prefab) { var container = new GameObject($"{prefab.name}_Pool").transform; container.SetParent(transform); return container; } private GameObject CreateInstance(PoolRecord record) { GameObject instanceObject = Instantiate(record.Prefab, record.Container); instanceObject.name = record.Prefab.name; instanceObject.SetActive(false); allInstances[instanceObject] = record; return instanceObject; } private void OnGetFromPool(PoolRecord record, GameObject obj) { activeInstances[obj] = record; obj.transform.SetParent(null); obj.SetActive(true); } private void OnReleaseToPool(PoolRecord record, GameObject obj) { activeInstances.Remove(obj); obj.SetActive(false); obj.transform.SetParent(record.Container); } private void OnDestroyPooledObject(GameObject obj) { activeInstances.Remove(obj); allInstances.Remove(obj); if (obj != null) Destroy(obj); } private static void Prewarm(PoolRecord record, int count) { if (count <= 0) return; var warmedObjects = new List(count); for (int i = 0; i < count; i++) { warmedObjects.Add(record.Pool.Get()); } for (int i = 0; i < warmedObjects.Count; i++) { record.Pool.Release(warmedObjects[i]); } } internal sealed class PoolRecord { public GameObject Prefab; public Transform Container; public ObjectPool Pool; } } }