chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
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
{
public static VFXPool Instance { get; private set; }
/// <summary>全局兜底超时(秒)。防止循环粒子永不回池。</summary>
[SerializeField, Min(1f)] private float _globalMaxLifetime = 10f;
private readonly Dictionary<AssetReferenceGameObject, Queue<ParticleSystem>> _pools
= new Dictionary<AssetReferenceGameObject, Queue<ParticleSystem>>();
private void Awake() => Instance = this;
/// <summary>
/// 在世界坐标播放一次特效Fire-and-forgetCoroutine 自动回池)。
/// </summary>
/// <param name="maxLifetime">&gt;0 时覆盖全局超时≤0 时使用 _globalMaxLifetime。</param>
public void Play(AssetReferenceGameObject vfxRef,
Vector3 position,
Quaternion rotation = default,
float maxLifetime = 0f)
{
StartCoroutine(PlayCoroutine(vfxRef, position, rotation, maxLifetime));
}
/// <summary>预热:预先创建若干实例避免首次播放卡顿。</summary>
public void Warmup(AssetReferenceGameObject vfxRef, int count)
{
StartCoroutine(WarmupCoroutine(vfxRef, count));
}
// ── 内部实现 ─────────────────────────────────────────────────────────────
private IEnumerator PlayCoroutine(AssetReferenceGameObject vfxRef,
Vector3 position,
Quaternion rotation,
float maxLifetime)
{
ParticleSystem ps = null;
// 从池中取或异步创建
if (TryDequeue(vfxRef, out ps))
{
// 直接使用
}
else
{
// 异步加载并实例化
var op = Addressables.InstantiateAsync(vfxRef, transform);
yield return op;
if (op.Result == null)
{
Debug.LogError($"[VFXPool] Failed to instantiate VFX: {vfxRef.RuntimeKey}");
yield break;
}
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();
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);
Debug.LogWarning(
$"[VFXPool] '{vfxRef.RuntimeKey}' 超过 {limit:F1}s 强制回收。" +
" 请检查粒子是否设为 Loop 或 Duration 过长。");
}
ps.gameObject.SetActive(false);
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);
}
}
}