using System; using System.Collections.Generic; using UnityEngine; using Zenject; namespace FlappyBird.Core { public class ObjectPoolManager : IObjectPoolManager, IInitializable { private readonly DiContainer _container; private readonly Dictionary> _pools = new Dictionary>(); private readonly Dictionary _prefabs = new Dictionary(); private readonly Dictionary _poolRoots = new Dictionary(); private Transform _mainPoolRoot; [Inject] public ObjectPoolManager(DiContainer container) { _container = container; } /// /// Initializes the main root transform to keep the scene hierarchy clean. /// public void Initialize() { _mainPoolRoot = new GameObject("ObjectPools").transform; UnityEngine.Object.DontDestroyOnLoad(_mainPoolRoot); } public void RegisterPool(GameObject prefab, int initialSize = 0) where T : Component, IPoolable { Type type = typeof(T); if (_pools.ContainsKey(type)) { Debug.LogWarning($"[ObjectPoolManager] Pool for type {type.Name} is already registered."); return; } _prefabs[type] = prefab; _pools[type] = new Queue(); // Create a specific parent transform for this type GameObject rootGo = new GameObject($"{type.Name}_Pool"); // Check if prefab is a UI element (RectTransform) if (prefab.transform is RectTransform) { // UI elements need to stay under a Canvas even when pooled, // or be moved to a hidden Canvas to stay "initialized" rootGo.AddComponent(); } Transform typeRoot = rootGo.transform; typeRoot.SetParent(_mainPoolRoot); _poolRoots[type] = typeRoot; // Pre-warm the pool for (int i = 0; i < initialSize; i++) { T instance = CreateInstance(); instance.gameObject.SetActive(false); _pools[type].Enqueue(instance); } } public T Get() where T : Component, IPoolable { Type type = typeof(T); if (!_pools.TryGetValue(type, out Queue pool)) { throw new InvalidOperationException($"Pool for type {type.Name} is not registered."); } T instance = (pool.Count == 0) ? CreateInstance() : pool.Dequeue() as T; instance.gameObject.SetActive(true); instance.OnSpawned(); return instance; } /// /// Returns an item to its respective pool. /// Automatically handles UI parenting back to the pool root. /// public void Return(T item) where T : Component, IPoolable { if (item == null) return; Type type = typeof(T); if (!_pools.TryGetValue(type, out Queue pool)) { // Fallback for cases where T might be an interface or base class type = item.GetType(); if (!_pools.TryGetValue(type, out pool)) { Debug.LogError($"[ObjectPoolManager] No pool registered for {type.Name}"); return; } } item.OnDespawned(); // Re-parent to the specific pool root to keep Hierarchy clean item.transform.SetParent(_poolRoots[type], false); item.gameObject.SetActive(false); pool.Enqueue(item); } private T CreateInstance() where T : Component, IPoolable { Type type = typeof(T); GameObject prefab = _prefabs[type]; Transform root = _poolRoots[type]; // Zenject InstantiatePrefab ensures [Inject] works inside the prefab (needed for ScorePopupView) GameObject instance = _container.InstantiatePrefab(prefab, root); T component = instance.GetComponent(); if (component == null) { component = instance.AddComponent(); } return component; } } }