好多新内容

This commit is contained in:
2026-07-22 05:35:53 +08:00
parent b5d1cdcbc5
commit 15649d02f8
128 changed files with 5956 additions and 673 deletions
@@ -43,6 +43,9 @@ public class TrackJudgeHitEffectController : MonoBehaviour
private readonly Queue<GameObject> particlePool = new Queue<GameObject>();
private Transform particlePoolRoot;
private readonly HashSet<GameObject> rentedParticles = new HashSet<GameObject>();
private readonly Dictionary<GameObject, int> particleRentIds = new Dictionary<GameObject, int>();
private int particleRentCounter;
// Per-instance cache: the particle systems and computed lifetime are resolved once
// when an instance is created, so the burst hot path never calls
@@ -72,6 +75,21 @@ public class TrackJudgeHitEffectController : MonoBehaviour
}
}
private void OnDisable()
{
ReturnAllRentedParticles();
}
private void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
ReturnAllRentedParticles();
}
public static void PlayTrackHitFx(int trackIndex)
{
var controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny<TrackJudgeHitEffectController>();
@@ -271,7 +289,8 @@ public class TrackJudgeHitEffectController : MonoBehaviour
if (instance == null)
return;
instance.transform.SetParent(parent, false);
Transform safeParent = parent != null && parent.gameObject.activeInHierarchy ? parent : transform;
instance.transform.SetParent(safeParent, false);
instance.transform.localPosition = trackParticleLocalPosition;
instance.transform.localRotation = Quaternion.Euler(trackParticleLocalEulerAngles);
instance.transform.localScale = trackParticleLocalScale;
@@ -280,7 +299,7 @@ public class TrackJudgeHitEffectController : MonoBehaviour
Color tint = GetTrackColor(trackIndex);
ApplyParticleColor(entry, tint);
StartCoroutine(RecycleParticleRoutine(instance, entry, Mathf.Max(0.05f, entry.Lifetime)));
StartCoroutine(RecycleParticleRoutine(instance, entry, CurrentParticleRentToken(instance), Mathf.Max(0.05f, entry.Lifetime)));
}
// Prewarm the track particle pool during load so the first judge does not pay
@@ -354,27 +373,65 @@ public class TrackJudgeHitEffectController : MonoBehaviour
{
GameObject pooled = particlePool.Dequeue();
if (pooled != null && particleCache.TryGetValue(pooled, out entry))
{
pooled.transform.SetParent(null, false);
MarkParticleRented(pooled);
return pooled;
}
}
GameObject created = CreateParticleInstance();
if (created != null && particleCache.TryGetValue(created, out entry))
{
MarkParticleRented(created);
return created;
}
entry = null;
return created;
}
private IEnumerator RecycleParticleRoutine(GameObject instance, ParticleCacheEntry entry, float delay)
private int MarkParticleRented(GameObject instance)
{
if (useUnscaledTime)
yield return new WaitForSecondsRealtime(delay);
else
yield return GameplayClock.WaitForSeconds(delay);
if (instance == null)
return -1;
rentedParticles.Add(instance);
int token = ++particleRentCounter;
particleRentIds[instance] = token;
return token;
}
private int CurrentParticleRentToken(GameObject instance)
{
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);
}
private void ReturnParticleInstance(GameObject instance, ParticleCacheEntry entry)
{
if (instance == null)
return;
if (!rentedParticles.Remove(instance))
return;
particleRentIds.Remove(instance);
if (entry != null && entry.Systems != null)
{
foreach (var ps in entry.Systems)
@@ -391,6 +448,19 @@ public class TrackJudgeHitEffectController : MonoBehaviour
particlePool.Enqueue(instance);
}
private void ReturnAllRentedParticles()
{
var snapshot = new List<GameObject>(rentedParticles);
foreach (var instance in snapshot)
{
if (instance == null)
continue;
particleCache.TryGetValue(instance, out var entry);
ReturnParticleInstance(instance, entry);
}
}
private Color GetTrackColor(int trackIndex)
{
if (trackColors != null && trackIndex >= 0 && trackIndex < trackColors.Length)