using System; using System.Collections.Generic; using Cielonos.MainGame; using UnityEngine; namespace Cielonos.MainGame.Effects.Feedback { /// /// 时间缩放通道数据,用于事件传输。 /// [Serializable] public struct TimeScaleChannelData { public bool active; public TimeScaleMode mode; public float fixedValue; public AnimationCurve curve; public float remapZero; public float remapOne; /// /// 根据归一化进度计算当前通道的时间缩放值。 /// public float Evaluate(float normalizedTime) { if (!active) return 1f; if (mode == TimeScaleMode.Fixed) { return fixedValue; } float curveValue = curve != null ? curve.Evaluate(normalizedTime) : 0f; return Mathf.LerpUnclamped(remapZero, remapOne, curveValue); } } /// /// 时间缩放震动事件。 /// public struct TimeScaleShakeEvent { private static event ShakeDelegate OnEvent; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; } public delegate void ShakeDelegate( float duration, TimeScaleChannelData global, TimeScaleChannelData player, TimeScaleChannelData enemy, TimeScaleChannelData allied, TimeScaleChannelData nonPlayer, bool stop ); /// /// 注册震动监听。 /// public static void Register(ShakeDelegate callback) { OnEvent += callback; } /// /// 取消震动监听。 /// public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; } /// /// 触发时间缩放震动事件。 /// public static void Trigger( float duration, TimeScaleChannelData global = default, TimeScaleChannelData player = default, TimeScaleChannelData enemy = default, TimeScaleChannelData allied = default, TimeScaleChannelData nonPlayer = default, bool stop = false) { OnEvent?.Invoke(duration, global, player, enemy, allied, nonPlayer, stop); } } /// /// 时间缩放震动实例。 /// public class TimeScaleShakeInstance { public readonly float Duration; public readonly TimeScaleChannelData Global; public readonly TimeScaleChannelData Player; public readonly TimeScaleChannelData Enemy; public readonly TimeScaleChannelData Allied; public readonly TimeScaleChannelData NonPlayer; public float Timer; public TimeScaleShakeInstance( float duration, TimeScaleChannelData global, TimeScaleChannelData player, TimeScaleChannelData enemy, TimeScaleChannelData allied, TimeScaleChannelData nonPlayer) { Duration = duration; Global = global; Player = player; Enemy = enemy; Allied = allied; NonPlayer = nonPlayer; Timer = 0f; } public bool IsFinished => Timer >= Duration; } /// /// TimeManager 的时间缩放震动聚合器。 /// 管理多个并发时间缩放实例。 /// 当有活跃实例时,各通道取"最后激活"实例的值(last wins)。 /// 全部结束后恢复初始值。 /// [AddComponentMenu("SLS Utilities/Feedback Shakers/Time Scale Shaker")] public class TimeScaleShaker : MonoBehaviour { private float _initGlobal; private float _initPlayer; private float _initEnemy; private float _initAllied; private float _initNonPlayer; private bool _resolved; private readonly List _activeShakes = new List(); private void Awake() { _resolved = TryResolve(); } private void OnEnable() { TimeScaleShakeEvent.Register(OnShakeEvent); } private void OnDisable() { TimeScaleShakeEvent.Unregister(OnShakeEvent); StopAll(); } private void Update() { if (!_resolved || _activeShakes.Count == 0) return; if (TimeManager.Instance == null) return; float dt = Time.deltaTime; // 各通道取最后激活实例的值 float globalVal = _initGlobal; float playerVal = _initPlayer; float enemyVal = _initEnemy; float alliedVal = _initAllied; float nonPlayerVal = _initNonPlayer; bool hasGlobal = false, hasPlayer = false, hasEnemy = false; bool hasAllied = false, hasNonPlayer = false; for (int i = _activeShakes.Count - 1; i >= 0; i--) { TimeScaleShakeInstance shake = _activeShakes[i]; shake.Timer += dt; float t = Mathf.Clamp01(shake.Timer / shake.Duration); if (shake.Global.active) { globalVal = shake.Global.Evaluate(t); hasGlobal = true; } if (shake.Player.active) { playerVal = shake.Player.Evaluate(t); hasPlayer = true; } if (shake.Enemy.active) { enemyVal = shake.Enemy.Evaluate(t); hasEnemy = true; } if (shake.Allied.active) { alliedVal = shake.Allied.Evaluate(t); hasAllied = true; } if (shake.NonPlayer.active) { nonPlayerVal = shake.NonPlayer.Evaluate(t); hasNonPlayer = true; } if (shake.IsFinished) { _activeShakes.RemoveAt(i); } } if (hasGlobal) TimeManager.Instance.globalTimeScale.Value = globalVal; if (hasPlayer) TimeManager.Instance.playerTimeScale.Value = playerVal; if (hasEnemy) TimeManager.Instance.enemyTimeScale.Value = enemyVal; if (hasAllied) TimeManager.Instance.alliedMinionTimeScale.Value = alliedVal; if (hasNonPlayer) TimeManager.Instance.nonPlayerTimeScale.Value = nonPlayerVal; if (_activeShakes.Count == 0) { Restore(); } } private void OnShakeEvent( float duration, TimeScaleChannelData global, TimeScaleChannelData player, TimeScaleChannelData enemy, TimeScaleChannelData allied, TimeScaleChannelData nonPlayer, bool stop) { if (stop) { StopAll(); return; } if (!_resolved) _resolved = TryResolve(); if (!_resolved) return; _activeShakes.Add(new TimeScaleShakeInstance( duration, global, player, enemy, allied, nonPlayer )); } private bool TryResolve() { if (TimeManager.Instance == null) return false; _initGlobal = TimeManager.Instance.globalTimeScale.Value; _initPlayer = TimeManager.Instance.playerTimeScale.Value; _initEnemy = TimeManager.Instance.enemyTimeScale.Value; _initAllied = TimeManager.Instance.alliedMinionTimeScale.Value; _initNonPlayer = TimeManager.Instance.nonPlayerTimeScale.Value; return true; } private void Restore() { if (TimeManager.Instance == null) return; TimeManager.Instance.globalTimeScale.Value = _initGlobal; TimeManager.Instance.playerTimeScale.Value = _initPlayer; TimeManager.Instance.enemyTimeScale.Value = _initEnemy; TimeManager.Instance.alliedMinionTimeScale.Value = _initAllied; TimeManager.Instance.nonPlayerTimeScale.Value = _initNonPlayer; } private void StopAll() { _activeShakes.Clear(); Restore(); } } }