摄像机区域的架构改动
This commit is contained in:
152
Assets/_Game/Scripts/VFX/VFXPool.cs
Normal file
152
Assets/_Game/Scripts/VFX/VFXPool.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace BaseGames.VFX
|
||||
{
|
||||
/// <summary>
|
||||
/// ParticleSystem 专用对象池。挂在 Persistent 场景 [VFXPool] GameObject 上。
|
||||
/// 粒子播放完成(或超过 MaxLifetime)后自动回池,调用方无需手动归还。
|
||||
/// 不依赖 UniTask,使用 Coroutine 驱动回收。
|
||||
/// </summary>
|
||||
public class VFXPool : MonoBehaviour, IVFXPoolService
|
||||
{
|
||||
|
||||
/// <summary>全局兜底超时(秒)。防止循环粒子永不回池。</summary>
|
||||
[SerializeField, Min(1f)] private float _globalMaxLifetime = 10f;
|
||||
|
||||
private readonly Dictionary<AssetReferenceGameObject, Queue<ParticleSystem>> _pools
|
||||
= new Dictionary<AssetReferenceGameObject, Queue<ParticleSystem>>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
BaseGames.Core.ServiceLocator.Register<IVFXPoolService>(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
BaseGames.Core.ServiceLocator.Unregister<IVFXPoolService>(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在世界坐标播放一次特效(Fire-and-forget,Coroutine 自动回池)。
|
||||
/// </summary>
|
||||
/// <param name="maxLifetime">>0 时覆盖全局超时;≤0 时使用 _globalMaxLifetime。</param>
|
||||
public void Play(AssetReferenceGameObject vfxRef,
|
||||
Vector3 position,
|
||||
Quaternion rotation = default,
|
||||
float maxLifetime = 0f)
|
||||
{
|
||||
// 池中有现成实例:同步播放,无需等待 Addressables 异步加载
|
||||
if (TryDequeue(vfxRef, out var ps))
|
||||
StartCoroutine(PlayImmediate(vfxRef, ps, position, rotation, maxLifetime));
|
||||
else
|
||||
StartCoroutine(PlayLoadAsync(vfxRef, position, rotation, maxLifetime));
|
||||
}
|
||||
|
||||
/// <summary>预热:预先创建若干实例避免首次播放卡顿。</summary>
|
||||
public void Warmup(AssetReferenceGameObject vfxRef, int count)
|
||||
{
|
||||
StartCoroutine(WarmupCoroutine(vfxRef, count));
|
||||
}
|
||||
|
||||
// ── 内部实现 ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>池命中路径:跳过异步加载,直接定位+播放。</summary>
|
||||
private IEnumerator PlayImmediate(AssetReferenceGameObject vfxRef,
|
||||
ParticleSystem ps,
|
||||
Vector3 position,
|
||||
Quaternion rotation,
|
||||
float maxLifetime)
|
||||
{
|
||||
ps.transform.SetPositionAndRotation(position, rotation);
|
||||
ps.gameObject.SetActive(true);
|
||||
ps.Play();
|
||||
yield return WaitAndReturn(ps, vfxRef, maxLifetime);
|
||||
}
|
||||
|
||||
/// <summary>池未命中路径:异步加载实例后播放。</summary>
|
||||
private IEnumerator PlayLoadAsync(AssetReferenceGameObject vfxRef,
|
||||
Vector3 position,
|
||||
Quaternion rotation,
|
||||
float maxLifetime)
|
||||
{
|
||||
var op = Addressables.InstantiateAsync(vfxRef, transform);
|
||||
yield return op;
|
||||
if (op.Result == null)
|
||||
{
|
||||
Debug.LogError($"[VFXPool] Failed to instantiate VFX: {vfxRef.RuntimeKey}");
|
||||
yield break;
|
||||
}
|
||||
var ps = op.Result.GetComponent<ParticleSystem>();
|
||||
if (ps == null)
|
||||
{
|
||||
Debug.LogError($"[VFXPool] No ParticleSystem on VFX prefab: {vfxRef.RuntimeKey}");
|
||||
Addressables.ReleaseInstance(op.Result);
|
||||
yield break;
|
||||
}
|
||||
ps.gameObject.SetActive(false);
|
||||
|
||||
ps.transform.SetPositionAndRotation(position, rotation);
|
||||
ps.gameObject.SetActive(true);
|
||||
ps.Play();
|
||||
yield return WaitAndReturn(ps, vfxRef, maxLifetime);
|
||||
}
|
||||
|
||||
/// <summary>等待粒子结束(或超时)后回池。</summary>
|
||||
private IEnumerator WaitAndReturn(ParticleSystem ps,
|
||||
AssetReferenceGameObject vfxRef,
|
||||
float maxLifetime)
|
||||
{
|
||||
float limit = maxLifetime > 0f ? maxLifetime : _globalMaxLifetime;
|
||||
float elapsed = 0f;
|
||||
|
||||
while (elapsed < limit && ps.IsAlive(true))
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (ps.IsAlive(true))
|
||||
{
|
||||
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
if (vfxRef != null)
|
||||
Debug.LogWarning(
|
||||
$"[VFXPool] '{vfxRef.RuntimeKey}' 超过 {limit:F1}s 强制回收。" +
|
||||
" 请检查粒子是否设为 Loop 或 Duration 过长。");
|
||||
}
|
||||
|
||||
ps.gameObject.SetActive(false);
|
||||
if (vfxRef != null) Enqueue(vfxRef, ps);
|
||||
}
|
||||
|
||||
private IEnumerator WarmupCoroutine(AssetReferenceGameObject vfxRef, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var op = Addressables.InstantiateAsync(vfxRef, transform);
|
||||
yield return op;
|
||||
if (op.Result == null) continue;
|
||||
var ps = op.Result.GetComponent<ParticleSystem>();
|
||||
if (ps == null) { Addressables.ReleaseInstance(op.Result); continue; }
|
||||
ps.gameObject.SetActive(false);
|
||||
Enqueue(vfxRef, ps);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryDequeue(AssetReferenceGameObject key, out ParticleSystem ps)
|
||||
{
|
||||
ps = null;
|
||||
return _pools.TryGetValue(key, out var q) && q.Count > 0 && (ps = q.Dequeue()) != null;
|
||||
}
|
||||
|
||||
private void Enqueue(AssetReferenceGameObject key, ParticleSystem ps)
|
||||
{
|
||||
if (!_pools.ContainsKey(key))
|
||||
_pools[key] = new Queue<ParticleSystem>();
|
||||
_pools[key].Enqueue(ps);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user