using System.Collections.Generic; using UnityEngine; /// /// 特效专用对象池 — 按 prefab.GetInstanceID() 分桶 /// 粒子播放完自动回池(通过 EffectAutoReturn 组件检测) /// 用法: /// var go = EffectPool.Instance.Get(prefab, pos, rot, parent); /// // 无需手动回收,EffectAutoReturn 会在粒子播完后自动回池 /// EffectPool.Instance.ClearAll(); // 切场景时彻底清理 /// public class EffectPool : Singleton { // prefab.GetInstanceID() → 该 prefab 的池 private Dictionary dic = new Dictionary(); // 活跃特效映射:特效GameObject → prefab.GetInstanceID() private Dictionary activeMap = new Dictionary(); private const int MaxCachePerPrefab = 100; private GameObject poolRoot; /// 从池中取一个特效(池空则 Instantiate),取出后自动播放并在播完时回池 public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent) { return Get(prefab, position, rotation, parent, autoReturn: true); } /// /// 从池中取一个特效。 /// autoReturn=true: 粒子特效,EffectAutoReturn 检测粒子播完自动回池 /// autoReturn=false: 动画/UI 驱动特效(如 BJBase/DieEffect_TSY),由动画事件手动调用 Return() /// public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent, bool autoReturn) { if (prefab == null) return null; int prefabID = prefab.GetInstanceID(); GameObject obj = null; if (dic.TryGetValue(prefabID, out var data)) { // 循环跳过已销毁的坏对象 while (data.poolList.Count > 0) { obj = data.GetObj(); if (obj != null) break; obj = null; } } if (obj != null) { 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); } activeMap[obj] = prefabID; if (autoReturn) { // 粒子特效:挂上自动回收组件(已挂则复用) var auto = obj.GetComponent(); if (auto == null) auto = obj.AddComponent(); auto.prefabID = prefabID; auto.Play(); } // autoReturn=false: 动画/UI 驱动特效,不挂自动回收组件 // 由动画事件(coinEvent/ScoreNumScrollEvent 等)调用 EffectPool.Instance.Return(gameObject) 回池 return obj; } /// 回收特效到池(通常由 EffectAutoReturn 自动调用) public void Return(GameObject obj) { if (obj == null) return; 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; } 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; } } private GameObject EnsurePoolRoot() { if (poolRoot == null) { poolRoot = new GameObject("[EffectPool]"); poolRoot.SetActive(false); } return poolRoot; } } /// 挂在池内特效上,粒子播放完自动回池 public class EffectAutoReturn : MonoBehaviour { [HideInInspector] public int prefabID; private ParticleSystem[] particles; private float checkTimer; private const float MinPlayTime = 0.3f; // 至少播 0.3s 再检测,避免第一帧 IsAlive=false 误判 private const float MaxLifetime = 6f; // ⭐ 兜底:超过 6 秒强制回池,防止 looping 残留导致特效永生 /// 重新播放所有粒子 public void Play() { if (particles == null || particles.Length == 0) particles = GetComponentsInChildren(true); checkTimer = 0f; foreach (var p in particles) { if (p != null) { // ⭐ 强制关闭 looping:有些预制体子级 ParticleSystem 设了 looping=1, // 会导致 IsAlive(true) 永远返回 true,特效一直累加直至内存爆满死机 var main = p.main; main.loop = false; p.Clear(true); p.Play(true); } } } void Update() { checkTimer += Time.deltaTime; if (checkTimer < MinPlayTime) return; // ⭐ 6 秒兜底:无论是否还存活,超过 6 秒强制回池 if (checkTimer >= MaxLifetime) { EffectPool.Instance.Return(gameObject); return; } // 所有粒子都播完 → 回池 if (particles == null || particles.Length == 0) { EffectPool.Instance.Return(gameObject); return; } bool anyAlive = false; foreach (var p in particles) { if (p != null && p.IsAlive(true)) { anyAlive = true; break; } } if (!anyAlive) { EffectPool.Instance.Return(gameObject); } } }