using System.Collections.Generic; using SLSUtilities.Feedback; using Unity.Cinemachine; using UnityEngine; namespace Cielonos.MainGame.Effects.Feedback { public class CameraDistanceShakeInstance : ShakeInstanceBase { public FloatCurveChannel distanceCurve; public CameraDistanceShakeInstance(FeedbackTimeSettings timeSettings, IFeedbackTimeProvider timeProvider, FloatCurveChannel distanceCurve, float duration) : base(timeSettings, timeProvider, duration) { this.distanceCurve = distanceCurve; } } public struct CameraDistanceShakeEvent { private static event ShakeDelegate OnEvent; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; } public delegate void ShakeDelegate( FeedbackContext feedbackContext, FloatCurveChannel distanceCurve, bool stop ); public static void Register(ShakeDelegate callback) { OnEvent += callback; } public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; } public static void Trigger( FeedbackContext feedbackContext, FloatCurveChannel distanceCurve, bool stop = false) { OnEvent?.Invoke(feedbackContext, distanceCurve, stop); } } [AddComponentMenu("Cielonos/Feedback Shakers/Camera Distance Shaker")] [RequireComponent(typeof(CinemachineCamera))] [RequireComponent(typeof(CinemachineOrbitalFollow))] public class CameraDistanceShaker : MonoBehaviour { private CinemachineCamera _camera; private CinemachineOrbitalFollow _orbitalFollow; public float initialDistance; [Tooltip("防止因为同类 Feedback 叠加导致距离过小")] public float minDistance = 1f; [Tooltip("防止因为同类 Feedback 叠加导致距离过大")] public float maxDistance = 30f; private readonly List _activeShakes = new List(); private void Awake() { _camera = GetComponent(); _orbitalFollow = GetComponent(); initialDistance = _orbitalFollow.Radius; } private void OnEnable() { CameraDistanceShakeEvent.Register(OnShakeEvent); } private void OnDisable() { CameraDistanceShakeEvent.Unregister(OnShakeEvent); StopAll(); } private void Update() { if (_orbitalFollow == null) return; if (_activeShakes.Count == 0) { SetDistance(initialDistance); return; } float additiveDistance = 0f; float absoluteDistance = 0f; bool hasAbsolute = false; for (int i = _activeShakes.Count - 1; i >= 0; i--) { CameraDistanceShakeInstance shake = _activeShakes[i]; shake.Tick(); float normalizedTime = shake.timer / shake.duration; if (shake.distanceCurve.relativeToInitial) { additiveDistance += shake.distanceCurve.Evaluate(normalizedTime); } else { absoluteDistance = shake.distanceCurve.Evaluate(normalizedTime); hasAbsolute = true; } if (shake.IsFinished) { _activeShakes.RemoveAt(i); } } float finalDistance = hasAbsolute ? absoluteDistance : initialDistance + additiveDistance; // 核心防御:钳制最终距离,防止多技能/反馈叠加导致的距离暴走 finalDistance = Mathf.Clamp(finalDistance, minDistance, maxDistance); SetDistance(finalDistance); } private void OnShakeEvent( FeedbackContext feedbackContext, FloatCurveChannel distanceCurve, bool stop) { if (stop) { StopAll(); return; } var instance = new CameraDistanceShakeInstance( feedbackContext.timeSettings, feedbackContext.player.TimeProvider, distanceCurve, feedbackContext.duration ); _activeShakes.Add(instance); } private void SetDistance(float distance) { _orbitalFollow.Radius = distance; } private void StopAll() { _activeShakes.Clear(); if (_orbitalFollow != null) { SetDistance(initialDistance); } } } }