This commit is contained in:
2026-10-14 19:39:24 +08:00
parent 4fd5a3bd45
commit 8498afd5cf
202 changed files with 12624 additions and 236 deletions
+217
View File
@@ -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);
}
}
}