ui基本完毕,修了一大把的bug
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using SpriteGlow;
|
||||
using UnityEngine;
|
||||
|
||||
public class TrackJudgeHitEffectController : MonoBehaviour
|
||||
{
|
||||
public static TrackJudgeHitEffectController Instance { get; private set; }
|
||||
|
||||
[Header("Track Sprite FX")]
|
||||
[Tooltip("0-4 maps to track 1-5.")]
|
||||
[SerializeField] private GameObject[] trackSpriteFxObjects = new GameObject[5];
|
||||
|
||||
[Header("Track Glow FX")]
|
||||
[Tooltip("0-4 maps to track 1-5.")]
|
||||
[SerializeField] private SpriteGlowEffect[] trackGlowEffects = new SpriteGlowEffect[5];
|
||||
[SerializeField, Range(0f, 1f)] private float trackGlowSpriteAlpha = 0f;
|
||||
|
||||
[Header("Particle FX")]
|
||||
[SerializeField] private GameObject trackParticlePrefab;
|
||||
[SerializeField] private Vector3 trackParticleLocalPosition = new Vector3(0f, -5.29f, -0.13f);
|
||||
[SerializeField] private Vector3 trackParticleLocalEulerAngles = new Vector3(70f, 0f, 0f);
|
||||
[SerializeField] private Vector3 trackParticleLocalScale = Vector3.one;
|
||||
|
||||
[Header("Track Colors")]
|
||||
[SerializeField] private Color[] trackColors = new Color[5]
|
||||
{
|
||||
new Color(1f, 0.44f, 0.42f, 1f),
|
||||
new Color(0.22f, 0.96f, 0.79f, 1f),
|
||||
new Color(1f, 0.88f, 0.63f, 1f),
|
||||
new Color(0.95f, 0.51f, 0.89f, 1f),
|
||||
new Color(0.48f, 0.98f, 1f, 1f)
|
||||
};
|
||||
|
||||
[Header("Fade")]
|
||||
[SerializeField, Range(0f, 1f)] private float initialAlpha = 10f / 255f;
|
||||
[SerializeField] private float fadeDuration = 0.18f;
|
||||
[SerializeField] private float glowFadeDuration = 0.18f;
|
||||
[SerializeField] private bool useUnscaledTime = true;
|
||||
|
||||
private readonly Dictionary<int, Coroutine> activeFadeRoutines = new Dictionary<int, Coroutine>();
|
||||
private readonly Dictionary<int, Coroutine> activeGlowFadeRoutines = new Dictionary<int, Coroutine>();
|
||||
|
||||
private readonly Queue<GameObject> particlePool = new Queue<GameObject>();
|
||||
private Transform particlePoolRoot;
|
||||
|
||||
// Per-instance cache: the particle systems and computed lifetime are resolved once
|
||||
// when an instance is created, so the burst hot path never calls
|
||||
// GetComponentsInChildren (an allocating call) per hit. Business behavior is
|
||||
// unchanged: same systems, same tint, same lifetime.
|
||||
private sealed class ParticleCacheEntry
|
||||
{
|
||||
public ParticleSystem[] Systems;
|
||||
public float Lifetime;
|
||||
}
|
||||
|
||||
private readonly Dictionary<GameObject, ParticleCacheEntry> particleCache =
|
||||
new Dictionary<GameObject, ParticleCacheEntry>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
ResetAllGlowAlpha();
|
||||
ResetAllGlowSpriteAlpha();
|
||||
}
|
||||
else if (Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void PlayTrackHitFx(int trackIndex)
|
||||
{
|
||||
var controller = Instance != null ? Instance : SceneObjectLookupCache.FindAny<TrackJudgeHitEffectController>();
|
||||
if (controller == null)
|
||||
return;
|
||||
|
||||
controller.PlayTrackHitFxInternal(trackIndex);
|
||||
}
|
||||
|
||||
private void PlayTrackHitFxInternal(int trackIndex)
|
||||
{
|
||||
if (trackIndex < 0 || trackIndex >= 5)
|
||||
return;
|
||||
|
||||
GameObject trackFxObject = GetTrackFxObject(trackIndex);
|
||||
if (trackFxObject == null)
|
||||
return;
|
||||
|
||||
if (activeGlowFadeRoutines.TryGetValue(trackIndex, out Coroutine glowRoutine) && glowRoutine != null)
|
||||
{
|
||||
StopCoroutine(glowRoutine);
|
||||
activeGlowFadeRoutines.Remove(trackIndex);
|
||||
}
|
||||
|
||||
if (activeFadeRoutines.TryGetValue(trackIndex, out Coroutine fadeRoutine) && fadeRoutine != null)
|
||||
{
|
||||
StopCoroutine(fadeRoutine);
|
||||
activeFadeRoutines.Remove(trackIndex);
|
||||
}
|
||||
|
||||
trackFxObject.SetActive(true);
|
||||
PlayGlowFx(trackIndex);
|
||||
|
||||
SpriteRenderer spriteRenderer = trackFxObject.GetComponent<SpriteRenderer>();
|
||||
if (spriteRenderer == null)
|
||||
{
|
||||
spriteRenderer = trackFxObject.GetComponentInChildren<SpriteRenderer>(true);
|
||||
}
|
||||
|
||||
if (spriteRenderer != null)
|
||||
{
|
||||
Color c = spriteRenderer.color;
|
||||
c.a = initialAlpha;
|
||||
spriteRenderer.color = c;
|
||||
activeFadeRoutines[trackIndex] = StartCoroutine(FadeSpriteRoutine(trackIndex, spriteRenderer));
|
||||
}
|
||||
|
||||
SpawnTrackParticle(trackIndex, trackFxObject.transform);
|
||||
}
|
||||
|
||||
private void ResetAllGlowAlpha()
|
||||
{
|
||||
if (trackGlowEffects == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < trackGlowEffects.Length; i++)
|
||||
{
|
||||
SetGlowAlpha(trackGlowEffects[i], 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetAllGlowSpriteAlpha()
|
||||
{
|
||||
if (trackGlowEffects == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < trackGlowEffects.Length; i++)
|
||||
{
|
||||
ResetGlowSpriteAlpha(trackGlowEffects[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject GetTrackFxObject(int trackIndex)
|
||||
{
|
||||
if (trackSpriteFxObjects == null || trackIndex < 0 || trackIndex >= trackSpriteFxObjects.Length)
|
||||
return null;
|
||||
|
||||
return trackSpriteFxObjects[trackIndex];
|
||||
}
|
||||
|
||||
private SpriteGlowEffect GetTrackGlowEffect(int trackIndex)
|
||||
{
|
||||
if (trackGlowEffects == null || trackIndex < 0 || trackIndex >= trackGlowEffects.Length)
|
||||
return null;
|
||||
|
||||
return trackGlowEffects[trackIndex];
|
||||
}
|
||||
|
||||
private void ResetGlowSpriteAlpha(SpriteGlowEffect glowEffect)
|
||||
{
|
||||
if (glowEffect == null || glowEffect.Renderer == null)
|
||||
return;
|
||||
|
||||
Color color = glowEffect.Renderer.color;
|
||||
color.a = trackGlowSpriteAlpha;
|
||||
glowEffect.Renderer.color = color;
|
||||
}
|
||||
|
||||
private void PlayGlowFx(int trackIndex)
|
||||
{
|
||||
SpriteGlowEffect glowEffect = GetTrackGlowEffect(trackIndex);
|
||||
if (glowEffect == null)
|
||||
return;
|
||||
|
||||
ResetGlowSpriteAlpha(glowEffect);
|
||||
SetGlowAlpha(glowEffect, 1f);
|
||||
|
||||
if (activeGlowFadeRoutines.TryGetValue(trackIndex, out Coroutine routine) && routine != null)
|
||||
{
|
||||
StopCoroutine(routine);
|
||||
activeGlowFadeRoutines.Remove(trackIndex);
|
||||
}
|
||||
|
||||
activeGlowFadeRoutines[trackIndex] = StartCoroutine(FadeGlowRoutine(trackIndex, glowEffect));
|
||||
}
|
||||
|
||||
private void SetGlowAlpha(SpriteGlowEffect glowEffect, float alpha)
|
||||
{
|
||||
if (glowEffect == null)
|
||||
return;
|
||||
|
||||
Color color = glowEffect.GlowColor;
|
||||
color.a = alpha;
|
||||
glowEffect.GlowColor = color;
|
||||
}
|
||||
|
||||
private IEnumerator FadeSpriteRoutine(int trackIndex, SpriteRenderer spriteRenderer)
|
||||
{
|
||||
float duration = Mathf.Max(0.01f, fadeDuration);
|
||||
float elapsed = 0f;
|
||||
|
||||
Color startColor = spriteRenderer != null ? spriteRenderer.color : Color.white;
|
||||
Color endColor = startColor;
|
||||
endColor.a = 0f;
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
if (spriteRenderer == null)
|
||||
yield break;
|
||||
|
||||
elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
|
||||
Color c = Color.LerpUnclamped(startColor, endColor, t);
|
||||
spriteRenderer.color = c;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (spriteRenderer != null)
|
||||
{
|
||||
Color c = spriteRenderer.color;
|
||||
c.a = 0f;
|
||||
spriteRenderer.color = c;
|
||||
}
|
||||
|
||||
activeFadeRoutines.Remove(trackIndex);
|
||||
}
|
||||
|
||||
private IEnumerator FadeGlowRoutine(int trackIndex, SpriteGlowEffect glowEffect)
|
||||
{
|
||||
float duration = Mathf.Max(0.01f, glowFadeDuration);
|
||||
float elapsed = 0f;
|
||||
|
||||
Color startColor = glowEffect != null ? glowEffect.GlowColor : Color.white;
|
||||
Color endColor = startColor;
|
||||
endColor.a = 0f;
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
if (glowEffect == null)
|
||||
yield break;
|
||||
|
||||
elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
|
||||
Color c = Color.LerpUnclamped(startColor, endColor, t);
|
||||
glowEffect.GlowColor = c;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (glowEffect != null)
|
||||
{
|
||||
Color c = glowEffect.GlowColor;
|
||||
c.a = 0f;
|
||||
glowEffect.GlowColor = c;
|
||||
}
|
||||
|
||||
activeGlowFadeRoutines.Remove(trackIndex);
|
||||
}
|
||||
|
||||
private void SpawnTrackParticle(int trackIndex, Transform parent)
|
||||
{
|
||||
if (trackParticlePrefab == null || parent == null)
|
||||
return;
|
||||
|
||||
GameObject instance = RentParticleInstance(out ParticleCacheEntry entry);
|
||||
if (instance == null)
|
||||
return;
|
||||
|
||||
instance.transform.SetParent(parent, false);
|
||||
instance.transform.localPosition = trackParticleLocalPosition;
|
||||
instance.transform.localRotation = Quaternion.Euler(trackParticleLocalEulerAngles);
|
||||
instance.transform.localScale = trackParticleLocalScale;
|
||||
instance.SetActive(true);
|
||||
|
||||
Color tint = GetTrackColor(trackIndex);
|
||||
ApplyParticleColor(entry, tint);
|
||||
|
||||
StartCoroutine(RecycleParticleRoutine(instance, entry, Mathf.Max(0.05f, entry.Lifetime)));
|
||||
}
|
||||
|
||||
// Prewarm the track particle pool during load so the first judge does not pay
|
||||
// the Instantiate + first-Play() cost on the hot path. Business behavior is
|
||||
// unchanged: the same prefab is used, instances are just reused instead of
|
||||
// being created and destroyed per hit.
|
||||
public void PrewarmTrackParticles(int count)
|
||||
{
|
||||
if (trackParticlePrefab == null || count <= 0)
|
||||
return;
|
||||
|
||||
EnsurePoolRoot();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GameObject instance = CreateParticleInstance();
|
||||
if (instance == null)
|
||||
continue;
|
||||
instance.transform.SetParent(particlePoolRoot, false);
|
||||
instance.SetActive(false);
|
||||
particlePool.Enqueue(instance);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsurePoolRoot()
|
||||
{
|
||||
if (particlePoolRoot == null)
|
||||
{
|
||||
var root = new GameObject("TrackParticlePool");
|
||||
root.transform.SetParent(transform, false);
|
||||
root.SetActive(false);
|
||||
particlePoolRoot = root.transform;
|
||||
}
|
||||
}
|
||||
|
||||
// Central instance factory: instantiates the prefab, resolves and caches its
|
||||
// particle systems + computed lifetime once, and leaves it stopped/cleared.
|
||||
private GameObject CreateParticleInstance()
|
||||
{
|
||||
if (trackParticlePrefab == null)
|
||||
return null;
|
||||
|
||||
GameObject instance = Instantiate(trackParticlePrefab);
|
||||
var systems = instance.GetComponentsInChildren<ParticleSystem>(true);
|
||||
if (systems == null)
|
||||
systems = new ParticleSystem[0];
|
||||
|
||||
var entry = new ParticleCacheEntry
|
||||
{
|
||||
Systems = systems,
|
||||
Lifetime = ComputeLifetime(systems)
|
||||
};
|
||||
|
||||
// Warm the play path once (forces emission buffer allocation and first-Play
|
||||
// cost during load), then reset to a clean stopped state ready for reuse.
|
||||
foreach (var ps in systems)
|
||||
{
|
||||
if (ps == null) continue;
|
||||
ps.Play(true);
|
||||
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
ps.Clear(true);
|
||||
}
|
||||
|
||||
particleCache[instance] = entry;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private GameObject RentParticleInstance(out ParticleCacheEntry entry)
|
||||
{
|
||||
while (particlePool.Count > 0)
|
||||
{
|
||||
GameObject pooled = particlePool.Dequeue();
|
||||
if (pooled != null && particleCache.TryGetValue(pooled, out entry))
|
||||
return pooled;
|
||||
}
|
||||
|
||||
GameObject created = CreateParticleInstance();
|
||||
if (created != null && particleCache.TryGetValue(created, out entry))
|
||||
return created;
|
||||
|
||||
entry = null;
|
||||
return created;
|
||||
}
|
||||
|
||||
private IEnumerator RecycleParticleRoutine(GameObject instance, ParticleCacheEntry entry, float delay)
|
||||
{
|
||||
if (useUnscaledTime)
|
||||
yield return new WaitForSecondsRealtime(delay);
|
||||
else
|
||||
yield return new WaitForSeconds(delay);
|
||||
|
||||
if (instance == null)
|
||||
yield break;
|
||||
|
||||
if (entry != null && entry.Systems != null)
|
||||
{
|
||||
foreach (var ps in entry.Systems)
|
||||
{
|
||||
if (ps == null) continue;
|
||||
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
ps.Clear(true);
|
||||
}
|
||||
}
|
||||
|
||||
instance.SetActive(false);
|
||||
EnsurePoolRoot();
|
||||
instance.transform.SetParent(particlePoolRoot, false);
|
||||
particlePool.Enqueue(instance);
|
||||
}
|
||||
|
||||
private Color GetTrackColor(int trackIndex)
|
||||
{
|
||||
if (trackColors != null && trackIndex >= 0 && trackIndex < trackColors.Length)
|
||||
{
|
||||
return trackColors[trackIndex];
|
||||
}
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
private void ApplyParticleColor(ParticleCacheEntry entry, Color tint)
|
||||
{
|
||||
if (entry == null || entry.Systems == null)
|
||||
return;
|
||||
|
||||
foreach (var ps in entry.Systems)
|
||||
{
|
||||
if (ps == null) continue;
|
||||
var main = ps.main;
|
||||
main.startColor = tint;
|
||||
if (!ps.isPlaying)
|
||||
{
|
||||
ps.Play(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float ComputeLifetime(ParticleSystem[] systems)
|
||||
{
|
||||
float maxLifetime = 0.5f;
|
||||
if (systems == null)
|
||||
return maxLifetime;
|
||||
|
||||
foreach (var ps in systems)
|
||||
{
|
||||
if (ps == null) continue;
|
||||
var main = ps.main;
|
||||
float startLifetime = 0.5f;
|
||||
if (main.startLifetime.mode == ParticleSystemCurveMode.Constant)
|
||||
startLifetime = main.startLifetime.constant;
|
||||
else if (main.startLifetime.mode == ParticleSystemCurveMode.TwoConstants)
|
||||
startLifetime = main.startLifetime.constantMax;
|
||||
else
|
||||
startLifetime = main.startLifetime.constantMax;
|
||||
|
||||
maxLifetime = Mathf.Max(maxLifetime, main.duration + startLifetime + 0.25f);
|
||||
}
|
||||
|
||||
return maxLifetime;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user