using System.Collections.Generic; using UnityEngine; /// /// 子弹专用对象池 — 按 prefab.GetInstanceID() 分桶,与鱼的 Pool 完全隔离 /// 用法: /// var go = BulletPool.Instance.Get(prefab, pos, rot, parent); /// BulletPool.Instance.Return(go); // 代替 Destroy(单个回收) /// BulletPool.Instance.ClearAll(); // 换炮台/切场景调用:销毁所有活跃子弹 + 清池 /// prefabID 直接用 prefab.GetInstanceID()(Unity 内部 hash,全局唯一) /// 活跃子弹→prefabID 的映射用 Dictionary 存储,不挂组件不改 name /// public class BulletPool : Singleton { // prefab.GetInstanceID() → 该 prefab 的池 private Dictionary dic = new Dictionary(); // 活跃子弹映射:子弹GameObject → prefab.GetInstanceID() // key 用 GameObject 本身(Unity 重载了 GetHashCode/Equals,按实例区分) // value 是 prefabID,回收时凭此找到对应池 private Dictionary activeMap = new Dictionary(); // 每个池子的最大缓存数(超出直接 Destroy,防止内存膨胀) private const int MaxCachePerPrefab = 200; // 池子根节点(隐藏在场景里) private GameObject poolRoot; /// 从池中取一个子弹(池空则 Instantiate) public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent) { if (prefab == null) return null; int prefabID = prefab.GetInstanceID(); GameObject obj = null; if (dic.TryGetValue(prefabID, out var data)) { // ⭐ 循环跳过已销毁的坏对象(ClearAll 用 Destroy 延迟销毁,时序竞争中可能残留) while (data.poolList.Count > 0) { obj = data.GetObj(); if (obj != null) break; // Unity 重载 ==,能识别已 Destroy 的假 null obj = null; // 坏对象丢弃,继续取下一个 } } if (obj != null) { // ⭐ 必须先 SetParent 再设 position:模仿 Instantiate(prefab, pos, rot, parent) obj.transform.SetParent(parent, false); obj.transform.position = position; obj.transform.rotation = rotation; obj.SetActive(true); } else { obj = GameObject.Instantiate(prefab, position, rotation, parent); obj.SetActive(true); } // 记录映射:子弹实例 → prefabID(回收时凭此找到对应池) activeMap[obj] = prefabID; return obj; } /// 无 parent 重载 public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation) { return Get(prefab, position, rotation, null); } /// 回收子弹到池(超容量则 Destroy) public void Return(GameObject obj) { if (obj == null) return; // 取不到映射 → 非 pool 对象 → 兜底 Destroy if (!activeMap.TryGetValue(obj, out int prefabID) || prefabID == 0) { GameObject.Destroy(obj); return; } activeMap.Remove(obj); if (!dic.TryGetValue(prefabID, out var data)) dic[prefabID] = data = new BulletPoolData(EnsurePoolRoot().transform, prefabID); if (data.poolList.Count >= MaxCachePerPrefab) { GameObject.Destroy(obj); return; } // 重置子弹状态(若有 BullitMove 组件) var bm = obj.GetComponent(); if (bm != null) bm.ResetState(); data.PushObj(obj); } /// 预加载 N 个某 prefab 到池 public void Preload(GameObject prefab, int count) { if (prefab == null || count <= 0) return; int prefabID = prefab.GetInstanceID(); if (!dic.TryGetValue(prefabID, out var data)) dic[prefabID] = data = new BulletPoolData(EnsurePoolRoot().transform, prefabID); for (int i = 0; i < count; i++) { var obj = GameObject.Instantiate(prefab, EnsurePoolRoot().transform); // 预加载的对象直接入池,不进 activeMap(它们没被"借出") data.PushObj(obj); } } /// 销毁所有活跃子弹 + 清空所有池(换炮台/切场景调用) public void ClearAll() { // 销毁所有借出未归还的子弹 foreach (var kv in activeMap.Keys) { if (kv != null) GameObject.Destroy(kv); } activeMap.Clear(); // 清空所有池 foreach (var kv in dic.Values) kv.Clear(); dic.Clear(); if (poolRoot != null) { GameObject.Destroy(poolRoot); poolRoot = null; } } /// 只销毁活跃子弹,保留池缓存(换炮台时调用:清屏但池能复用) public void ClearActive() { foreach (var kv in activeMap.Keys) { if (kv != null) GameObject.Destroy(kv); } activeMap.Clear(); // ⭐ 不清 dic,池里的缓存保留,同 prefab 下次能复用 } private GameObject EnsurePoolRoot() { if (poolRoot == null) { poolRoot = new GameObject("[BulletPool]"); poolRoot.SetActive(false); // 池根节点隐藏,池内对象跟随隐藏 } return poolRoot; } } /// 单个 prefab 的子弹池数据 public class BulletPoolData { public Transform fatherObj; public List poolList; public BulletPoolData(Transform root, int id) { fatherObj = new GameObject("bulletPool_" + id).transform; fatherObj.SetParent(root, false); poolList = new List(); } public void PushObj(GameObject obj) { if (obj == null) return; poolList.Add(obj); obj.transform.SetParent(fatherObj, false); obj.SetActive(false); } public GameObject GetObj() { GameObject obj = poolList[poolList.Count - 1]; poolList.RemoveAt(poolList.Count - 1); return obj; } public void Clear() { foreach (var obj in poolList) { if (obj != null) GameObject.Destroy(obj); } poolList.Clear(); if (fatherObj != null) GameObject.Destroy(fatherObj.gameObject); } }