using System; using System.Collections.Generic; using Cielonos.MainGame; using SLSUtilities.Feedback; 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( FeedbackContext feedbackContext, float duration, int priority, 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( FeedbackContext feedbackContext, float duration, int priority = 0, TimeScaleChannelData global = default, TimeScaleChannelData player = default, TimeScaleChannelData enemy = default, TimeScaleChannelData allied = default, TimeScaleChannelData nonPlayer = default, bool stop = false) { OnEvent?.Invoke(feedbackContext, duration, priority, 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 readonly int Priority; public readonly int Sequence; public readonly FeedbackPlayer PlayerOwner; public readonly int RuntimeFeedbackId; public readonly int TrackIndex; public readonly int ClipIndex; public float Timer; public TimeScaleShakeInstance( FeedbackContext feedbackContext, float duration, int priority, int sequence, TimeScaleChannelData global, TimeScaleChannelData player, TimeScaleChannelData enemy, TimeScaleChannelData allied, TimeScaleChannelData nonPlayer) { Duration = duration; Global = global; Player = player; Enemy = enemy; Allied = allied; NonPlayer = nonPlayer; Priority = priority; Sequence = sequence; PlayerOwner = feedbackContext.player; RuntimeFeedbackId = feedbackContext.runtimeFeedbackId; TrackIndex = feedbackContext.trackIndex; ClipIndex = feedbackContext.clipIndex; Timer = 0f; } public bool IsFinished => Timer >= Duration; public bool Matches(FeedbackContext feedbackContext) { return PlayerOwner == feedbackContext.player && RuntimeFeedbackId == feedbackContext.runtimeFeedbackId && TrackIndex == feedbackContext.trackIndex && ClipIndex == feedbackContext.clipIndex; } } /// /// 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 int _nextSequence; 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; ApplyActiveShakes(Time.deltaTime); } private void ApplyActiveShakes(float deltaTime) { // 各通道取最后激活实例的值 float globalVal = _initGlobal; float playerVal = _initPlayer; float enemyVal = _initEnemy; float alliedVal = _initAllied; float nonPlayerVal = _initNonPlayer; TimeScaleShakeInstance globalWinner = null; TimeScaleShakeInstance playerWinner = null; TimeScaleShakeInstance enemyWinner = null; TimeScaleShakeInstance alliedWinner = null; TimeScaleShakeInstance nonPlayerWinner = null; for (int i = _activeShakes.Count - 1; i >= 0; i--) { TimeScaleShakeInstance shake = _activeShakes[i]; shake.Timer += deltaTime; if (shake.IsFinished) { _activeShakes.RemoveAt(i); continue; } if (shake.Global.active && HasHigherOrder(shake, globalWinner)) globalWinner = shake; if (shake.Player.active && HasHigherOrder(shake, playerWinner)) playerWinner = shake; if (shake.Enemy.active && HasHigherOrder(shake, enemyWinner)) enemyWinner = shake; if (shake.Allied.active && HasHigherOrder(shake, alliedWinner)) alliedWinner = shake; if (shake.NonPlayer.active && HasHigherOrder(shake, nonPlayerWinner)) nonPlayerWinner = shake; } if (globalWinner != null) globalVal = globalWinner.Global.Evaluate(GetNormalizedTime(globalWinner)); if (playerWinner != null) playerVal = playerWinner.Player.Evaluate(GetNormalizedTime(playerWinner)); if (enemyWinner != null) enemyVal = enemyWinner.Enemy.Evaluate(GetNormalizedTime(enemyWinner)); if (alliedWinner != null) alliedVal = alliedWinner.Allied.Evaluate(GetNormalizedTime(alliedWinner)); if (nonPlayerWinner != null) nonPlayerVal = nonPlayerWinner.NonPlayer.Evaluate(GetNormalizedTime(nonPlayerWinner)); TimeManager.Instance.globalTimeScale.Value = globalVal; TimeManager.Instance.playerTimeScale.Value = playerVal; TimeManager.Instance.enemyTimeScale.Value = enemyVal; TimeManager.Instance.alliedMinionTimeScale.Value = alliedVal; TimeManager.Instance.nonPlayerTimeScale.Value = nonPlayerVal; if (_activeShakes.Count == 0) { Restore(); } } private void OnShakeEvent( FeedbackContext feedbackContext, float duration, int priority, TimeScaleChannelData global, TimeScaleChannelData player, TimeScaleChannelData enemy, TimeScaleChannelData allied, TimeScaleChannelData nonPlayer, bool stop) { if (stop) { _activeShakes.RemoveAll(instance => instance != null && instance.Matches(feedbackContext)); if (_activeShakes.Count == 0) { Restore(); } else if (_resolved && TimeManager.Instance != null) { ApplyActiveShakes(0f); } return; } if (!_resolved) _resolved = TryResolve(); if (!_resolved) return; _activeShakes.Add(new TimeScaleShakeInstance( feedbackContext, duration, priority, ++_nextSequence, global, player, enemy, allied, nonPlayer )); ApplyActiveShakes(0f); } private static bool HasHigherOrder(TimeScaleShakeInstance candidate, TimeScaleShakeInstance current) { if (current == null) return true; if (candidate.Priority != current.Priority) return candidate.Priority > current.Priority; return candidate.Sequence > current.Sequence; } private static float GetNormalizedTime(TimeScaleShakeInstance shake) { return Mathf.Clamp01(shake.Timer / Mathf.Max(shake.Duration, 0.001f)); } 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(); } } }