using UnityEngine; public class GlobalAfterimageRuntimeController : MonoBehaviour { public static GlobalAfterimageRuntimeController Instance { get; private set; } [Header("Runtime Afterimage Defaults")] public float defaultStartOpacity = 0.5f; public float defaultFadeDuration = 0.22f; public float defaultEndScale = 1.12f; public bool useUnscaledTime = true; public bool triggerOnEnable = false; private static float sRuntimeOpacity; private static float sRuntimeScale = 1f; private static bool sCaptureRequested; private float activeStartOpacity; private float activeFadeDuration; private float activeEndScale = 1f; private float activeElapsed; private bool activeAfterimage; public static float RuntimeOpacity => sRuntimeOpacity; public static float RuntimeScale => sRuntimeScale; public static bool HasActiveAfterimage => sRuntimeOpacity > 0.0001f; private void Awake() { if (Instance == null) { Instance = this; return; } if (Instance != this) Destroy(gameObject); } private void OnEnable() { if (triggerOnEnable) TriggerAfterimage(); } private void Update() { float delta = useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime; UpdateAfterimage(delta); } public void TriggerAfterimage() { TriggerAfterimage(defaultStartOpacity, defaultFadeDuration, defaultEndScale); } public void TriggerAfterimage(float startOpacity, float fadeDuration, float endScale) { activeStartOpacity = Mathf.Clamp01(startOpacity); activeFadeDuration = Mathf.Max(0.0001f, fadeDuration); activeEndScale = Mathf.Max(1f, endScale); activeElapsed = 0f; activeAfterimage = activeStartOpacity > 0.0001f; sRuntimeOpacity = activeStartOpacity; sRuntimeScale = 1f; sCaptureRequested = activeAfterimage; } private void UpdateAfterimage(float delta) { if (!activeAfterimage) { sRuntimeOpacity = 0f; sRuntimeScale = 1f; return; } activeElapsed += delta; float normalized = Mathf.Clamp01(activeElapsed / activeFadeDuration); sRuntimeOpacity = Mathf.Lerp(activeStartOpacity, 0f, normalized); sRuntimeScale = Mathf.Lerp(1f, activeEndScale, normalized); if (normalized < 1f) return; activeAfterimage = false; sRuntimeOpacity = 0f; sRuntimeScale = 1f; } public static bool ConsumeCaptureRequest() { bool requested = sCaptureRequested; sCaptureRequested = false; return requested; } public static GlobalAfterimageRuntimeController EnsureInstance() { if (Instance != null) return Instance; GlobalAfterimageRuntimeController existing = SceneObjectLookupCache.FindFirst(); if (existing != null) { Instance = existing; return existing; } GameObject runtimeObject = new GameObject("__global_afterimage_runtime"); Instance = runtimeObject.AddComponent(); return Instance; } public static void Trigger(float startOpacity, float fadeDuration, float endScale) { EnsureInstance().TriggerAfterimage(startOpacity, fadeDuration, endScale); } public static void TriggerDefault() { EnsureInstance().TriggerAfterimage(); } }