200 lines
6.1 KiB
C#
200 lines
6.1 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// 特效专用对象池 — 按 prefab.GetInstanceID() 分桶
|
|
/// 粒子播放完自动回池(通过 EffectAutoReturn 组件检测)
|
|
/// 用法:
|
|
/// var go = EffectPool.Instance.Get(prefab, pos, rot, parent);
|
|
/// // 无需手动回收,EffectAutoReturn 会在粒子播完后自动回池
|
|
/// EffectPool.Instance.ClearAll(); // 切场景时彻底清理
|
|
/// </summary>
|
|
public class EffectPool : Singleton<EffectPool>
|
|
{
|
|
// prefab.GetInstanceID() → 该 prefab 的池
|
|
private Dictionary<int, BulletPoolData> dic = new Dictionary<int, BulletPoolData>();
|
|
|
|
// 活跃特效映射:特效GameObject → prefab.GetInstanceID()
|
|
private Dictionary<GameObject, int> activeMap = new Dictionary<GameObject, int>();
|
|
|
|
private const int MaxCachePerPrefab = 100;
|
|
|
|
private GameObject poolRoot;
|
|
|
|
/// <summary>从池中取一个特效(池空则 Instantiate),取出后自动播放并在播完时回池</summary>
|
|
public GameObject Get(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent)
|
|
{
|
|
return Get(prefab, position, rotation, parent, autoReturn: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从池中取一个特效。
|
|
/// autoReturn=true: 粒子特效,EffectAutoReturn 检测粒子播完自动回池
|
|
/// autoReturn=false: 动画/UI 驱动特效(如 BJBase/DieEffect_TSY),由动画事件手动调用 Return()
|
|
/// </summary>
|
|
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<EffectAutoReturn>();
|
|
if (auto == null)
|
|
auto = obj.AddComponent<EffectAutoReturn>();
|
|
auto.prefabID = prefabID;
|
|
auto.Play();
|
|
}
|
|
// autoReturn=false: 动画/UI 驱动特效,不挂自动回收组件
|
|
// 由动画事件(coinEvent/ScoreNumScrollEvent 等)调用 EffectPool.Instance.Return(gameObject) 回池
|
|
|
|
return obj;
|
|
}
|
|
|
|
/// <summary>回收特效到池(通常由 EffectAutoReturn 自动调用)</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>销毁所有活跃特效 + 清空所有池(切场景调用)</summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>挂在池内特效上,粒子播放完自动回池</summary>
|
|
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 残留导致特效永生
|
|
|
|
/// <summary>重新播放所有粒子</summary>
|
|
public void Play()
|
|
{
|
|
if (particles == null || particles.Length == 0)
|
|
particles = GetComponentsInChildren<ParticleSystem>(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);
|
|
}
|
|
}
|
|
}
|