using System.Collections.Generic; using SLSUtilities.Cinemachine; using SLSUtilities.Feedback; using Unity.Cinemachine; using UnityEngine; namespace Cielonos.MainGame.Effects.Feedback { /// /// 单个旋转震动实例的运行时状态。 /// public class CameraRotationShakeInstance : ShakeInstanceBase { public FloatCurveChannel intensityCurve; public Vector3 amplitude; public CameraRotationShakeInstance( FeedbackTimeSettings timeSettings, IFeedbackTimeProvider timeProvider, FloatCurveChannel intensityCurve, Vector3 amplitude, float duration) : base(timeSettings, timeProvider, duration) { this.intensityCurve = intensityCurve; this.amplitude = amplitude; } /// /// 计算当前帧的偏移量。 /// public Vector3 Evaluate() { float normalizedTime = duration > 0 ? timer / duration : 1f; float curveValue = intensityCurve.Evaluate(normalizedTime); return amplitude * curveValue; } } /// /// 摄像机旋转震动事件,用于解耦 Action 与 Shaker。 /// public struct CameraRotationShakeEvent { private static event ShakeDelegate OnEvent; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; } public delegate void ShakeDelegate( FeedbackContext feedbackContext, FloatCurveChannel intensityCurve, Vector3 amplitude, 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 intensityCurve, Vector3 amplitude, bool stop = false) { OnEvent?.Invoke(feedbackContext, intensityCurve, amplitude, stop); } } /// /// Cinemachine 旋转震动器。挂载于 CinemachineCamera 上, /// 监听 CameraRotationShakeEvent,驱动 CinemachineRotationOffset 实现叠加震动。 /// X/Y 作用于 FollowTarget 旋转,Z 作用于 Dutch 倾斜。 /// [AddComponentMenu("Cielonos/Feedback Shakers/Camera Rotation Shaker")] [RequireComponent(typeof(CinemachineCamera))] [RequireComponent(typeof(CinemachineRotationOffset))] public class CinemachineRotationShaker : MonoBehaviour { private CinemachineRotationOffset _rotationOffset; private Vector3 _initialRotation; private readonly List _activeShakes = new List(); private void Awake() { _rotationOffset = GetComponent(); _initialRotation = _rotationOffset.rotationOffset; } private void OnEnable() { CameraRotationShakeEvent.Register(OnShakeEvent); } private void OnDisable() { CameraRotationShakeEvent.Unregister(OnShakeEvent); StopAll(); } private void Update() { if (_rotationOffset == null) return; if (_activeShakes.Count == 0) { _rotationOffset.rotationOffset = _initialRotation; return; } Vector3 totalOffset = Vector3.zero; for (int i = _activeShakes.Count - 1; i >= 0; i--) { CameraRotationShakeInstance shake = _activeShakes[i]; shake.Tick(); totalOffset += shake.Evaluate(); if (shake.IsFinished) { _activeShakes.RemoveAt(i); } } _rotationOffset.rotationOffset = _initialRotation + totalOffset; } private void OnShakeEvent( FeedbackContext feedbackContext, FloatCurveChannel intensityCurve, Vector3 amplitude, bool stop) { if (stop) { StopAll(); return; } _activeShakes.Add(new CameraRotationShakeInstance( feedbackContext.timeSettings, feedbackContext.player.TimeProvider, intensityCurve, amplitude, feedbackContext.duration )); } private void StopAll() { _activeShakes.Clear(); if (_rotationOffset != null) { _rotationOffset.rotationOffset = _initialRotation; } } } }