1
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
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)
|
||||
{
|
||||
return GetCore(prefab, position, rotation, parent, autoReturn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从池中取一个特效,不设置位置/旋转——位置、大小完全保留预制体里配置好的
|
||||
/// (爆奖特效用,z 也用预制体自带的,不被生成位置带偏)。
|
||||
/// </summary>
|
||||
public GameObject Get(GameObject prefab, Transform parent, bool autoReturn)
|
||||
{
|
||||
return GetCore(prefab, null, null, parent, autoReturn);
|
||||
}
|
||||
|
||||
/// <summary>取用核心:position/rotation 传 null 时不设置,保留预制体配置好的变换</summary>
|
||||
private GameObject GetCore(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);
|
||||
// ⭐ 只在传入时才设置位置/旋转;不传则保留预制体配置好的位置和大小
|
||||
if (position.HasValue) obj.transform.position = position.Value;
|
||||
if (rotation.HasValue) obj.transform.rotation = rotation.Value;
|
||||
obj.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = position.HasValue
|
||||
? GameObject.Instantiate(prefab, position.Value, rotation ?? Quaternion.identity, parent)
|
||||
: GameObject.Instantiate(prefab, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user