备份,准备做双端

This commit is contained in:
FloatGaming
2026-07-30 23:15:58 +08:00
parent add675e45d
commit ec4ec53545
88 changed files with 4050 additions and 872 deletions
@@ -60,6 +60,18 @@ public class TrackJudgeHitEffectController : MonoBehaviour
private readonly Dictionary<GameObject, ParticleCacheEntry> particleCache =
new Dictionary<GameObject, ParticleCacheEntry>();
// 【性能】延迟回收改用 Update 轮询,替代每命中 StartCoroutine + new WaitForSecondsRealtime 的分配。
// dueUnscaledTime 用 Time.unscaledTime 记录到期时刻,与原 WaitForSecondsRealtime(realtime)语义一致;
// token 校验也原样保留,故回收时机与行为完全等效,仅消除每命中的协程/等待对象 GC。
private struct PendingParticleReturn
{
public GameObject Instance;
public ParticleCacheEntry Entry;
public int RentToken;
public float DueUnscaledTime;
}
private readonly List<PendingParticleReturn> pendingParticleReturns = new List<PendingParticleReturn>();
private void Awake()
{
if (Instance == null)
@@ -299,7 +311,39 @@ public class TrackJudgeHitEffectController : MonoBehaviour
Color tint = GetTrackColor(trackIndex);
ApplyParticleColor(entry, tint);
StartCoroutine(RecycleParticleRoutine(instance, entry, CurrentParticleRentToken(instance), Mathf.Max(0.05f, entry.Lifetime)));
// 入队延迟回收(Update 轮询),替代每命中 StartCoroutine + WaitForSecondsRealtime 分配。等效。
float delay = Mathf.Max(0.05f, entry.Lifetime);
pendingParticleReturns.Add(new PendingParticleReturn
{
Instance = instance,
Entry = entry,
RentToken = CurrentParticleRentToken(instance),
DueUnscaledTime = Time.unscaledTime + delay
});
}
private void Update()
{
if (pendingParticleReturns.Count == 0)
return;
float nowUnscaled = Time.unscaledTime;
for (int i = pendingParticleReturns.Count - 1; i >= 0; i--)
{
var pending = pendingParticleReturns[i];
if (nowUnscaled < pending.DueUnscaledTime)
continue;
pendingParticleReturns.RemoveAt(i);
// 与原 RecycleParticleRoutine 等效:实例已毁或已被重新租用(token 变了)则不回收。
if (pending.Instance == null)
continue;
if (CurrentParticleRentToken(pending.Instance) != pending.RentToken)
continue;
ReturnParticleInstance(pending.Instance, pending.Entry);
}
}
// Prewarm the track particle pool during load so the first judge does not pay
@@ -407,20 +451,8 @@ public class TrackJudgeHitEffectController : MonoBehaviour
return instance != null && particleRentIds.TryGetValue(instance, out int token) ? token : -1;
}
private IEnumerator RecycleParticleRoutine(GameObject instance, ParticleCacheEntry entry, int rentToken, float delay)
{
// Short visual FX should always recycle in real time. Tying them to
// GameplayClock can leak rented objects while the song clock is paused.
yield return new WaitForSecondsRealtime(Mathf.Max(0f, delay));
if (instance == null)
yield break;
if (CurrentParticleRentToken(instance) != rentToken)
yield break;
ReturnParticleInstance(instance, entry);
}
// 说明:原 RecycleParticleRoutine 协程(每命中 StartCoroutine + new WaitForSecondsRealtime)已由
// Update 轮询的 pendingParticleReturns 替代,回收时机(unscaled/realtime)与 token 校验语义完全一致。
private void ReturnParticleInstance(GameObject instance, ParticleCacheEntry entry)
{
@@ -459,6 +491,9 @@ public class TrackJudgeHitEffectController : MonoBehaviour
particleCache.TryGetValue(instance, out var entry);
ReturnParticleInstance(instance, entry);
}
// 所有租用粒子已强制回收,清空延迟回收队列避免残留项(与原协程在 OnDisable/OnDestroy 随之停止等效)。
pendingParticleReturns.Clear();
}
private Color GetTrackColor(int trackIndex)