using UnityEngine; using MoreMountains.Feedbacks; using MoreMountains.Tools; using System.Collections.Generic; #if MM_CINEMACHINE using Cinemachine; #elif MM_CINEMACHINE3 using Unity.Cinemachine; #endif namespace MoreMountains.FeedbacksForThirdParty { // 1. 定义震动实例类 (保存单个震动的状态) public class MMCinemachineRotationShakeInstance { public float Duration; public float Timer; public Vector3 Amplitude; public AnimationCurve Curve; public float Intensity; public TimescaleModes TimescaleMode; public MMCinemachineRotationShakeInstance(float duration, Vector3 amplitude, AnimationCurve curve, float intensity, TimescaleModes timescaleMode) { Duration = duration; Timer = 0f; Amplitude = amplitude; Curve = curve; Intensity = intensity; TimescaleMode = timescaleMode; } } // 2. 定义事件 public struct MMCinemachineRotationShakeEvent { static private event Delegate OnEvent; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; } public static void Register(Delegate callback) { OnEvent += callback; } public static void Unregister(Delegate callback) { OnEvent -= callback; } public delegate void Delegate(MMF_Feedback source, AnimationCurve shakeCurve, float duration, Vector3 rotationAmplitude, float remapMin, float remapMax, bool relative, float feedbacksIntensity = 1.0f, MMChannelData channelData = null, bool resetShakerValuesAfterShake = true, bool resetTargetValuesAfterShake = true, bool forwardDirection = true, TimescaleModes timescaleMode = TimescaleModes.Scaled, bool stop = false, bool restore = false); public static void Trigger(MMF_Feedback source, AnimationCurve shakeCurve, float duration, Vector3 rotationAmplitude, float remapMin, float remapMax, bool relative, float feedbacksIntensity = 1.0f, MMChannelData channelData = null, bool resetShakerValuesAfterShake = true, bool resetTargetValuesAfterShake = true, bool forwardDirection = true, TimescaleModes timescaleMode = TimescaleModes.Scaled, bool stop = false, bool restore = false) { OnEvent?.Invoke(source, shakeCurve, duration, rotationAmplitude, remapMin, remapMax, relative, feedbacksIntensity, channelData, resetShakerValuesAfterShake, resetTargetValuesAfterShake, forwardDirection, timescaleMode, stop, restore); } } /// /// Cinemachine Rotation Shaker (Additive / Mixing Support) /// X/Y applied to FollowTarget, Z applied to Camera Dutch. /// 支持多个 Feedback 同时叠加震动。 /// [AddComponentMenu("More Mountains/Feedbacks/Shakers/Cinemachine/MM Cinemachine Rotation Shaker (Additive)")] #if MM_CINEMACHINE [RequireComponent(typeof(CinemachineVirtualCamera))] #elif MM_CINEMACHINE3 [RequireComponent(typeof(CinemachineCamera))] #endif public class MMCinemachineRotationShaker : MMShaker { public Transform FollowTarget => _targetCamera.Follow; [MMInspectorGroup("Debug Info", true, 43)] [MMReadOnly] public int ActiveShakesCount = 0; #if MM_CINEMACHINE protected CinemachineVirtualCamera _targetCamera; #elif MM_CINEMACHINE3 protected CinemachineCamera _targetCamera; #endif protected float _initialDutch; protected Quaternion _initialTargetRotation; // 活跃震动列表 protected List _activeShakes = new List(); protected override void Initialization() { // 不调用 base.Initialization() 以完全接管 Update 逻辑 #if MM_CINEMACHINE _targetCamera = GetComponent(); #elif MM_CINEMACHINE3 _targetCamera = GetComponent(); #endif GrabInitialValues(); } protected override void GrabInitialValues() { if (_targetCamera != null) { #if MM_CINEMACHINE _initialDutch = _targetCamera.m_Lens.Dutch; #elif MM_CINEMACHINE3 _initialDutch = _targetCamera.Lens.Dutch; #endif } if (FollowTarget != null) { _initialTargetRotation = FollowTarget.localRotation; } } // 自定义 Update 处理叠加逻辑 protected override void Update() { if (_activeShakes.Count == 0) { Stop(); return; } float totalX = 0f; float totalY = 0f; float totalZ = 0f; // 倒序遍历列表 for (int i = _activeShakes.Count - 1; i >= 0; i--) { var shake = _activeShakes[i]; // 更新时间 float deltaTime = (shake.TimescaleMode == TimescaleModes.Scaled) ? Time.deltaTime : Time.unscaledDeltaTime; shake.Timer += deltaTime; // 计算当前强度 float percent = Mathf.Clamp01(shake.Timer / shake.Duration); float curveValue = shake.Curve.Evaluate(percent); float currentIntensity = curveValue * shake.Intensity; // 累加 Euler 角度 totalX += shake.Amplitude.x * currentIntensity; totalY += shake.Amplitude.y * currentIntensity; totalZ += shake.Amplitude.z * currentIntensity; // 移除过期震动 if (shake.Timer >= shake.Duration) { _activeShakes.RemoveAt(i); } } // 应用总偏移 ApplyRotation(totalX, totalY, totalZ); // 更新 Debug 计数 ActiveShakesCount = _activeShakes.Count; // 如果所有震动结束,强制归位 if (_activeShakes.Count == 0) { ApplyRotation(0f, 0f, 0f); } } protected virtual void ApplyRotation(float xOffset, float yOffset, float zOffset) { // Z -> Dutch (直接加法) if (_targetCamera != null) { #if MM_CINEMACHINE _targetCamera.m_Lens.Dutch = _initialDutch + zOffset; #elif MM_CINEMACHINE3 _targetCamera.Lens.Dutch = _initialDutch + zOffset; #endif } // X/Y -> FollowTarget (Quaternion 乘法) if (FollowTarget != null) { // 注意:这里我们将叠加后的 Euler 角度转换为 Quaternion,然后应用到初始旋转上 FollowTarget.localRotation = _initialTargetRotation * Quaternion.Euler(xOffset, yOffset, 0f); } } public virtual void OnMMCinemachineRotationShakeEvent(MMF_Feedback source, AnimationCurve shakeCurve, float duration, Vector3 rotationAmplitude, float remapMin, float remapMax, bool relative, float feedbacksIntensity = 1.0f, MMChannelData channelData = null, bool resetShakerValuesAfterShake = true, bool resetTargetValuesAfterShake = true, bool forwardDirection = true, TimescaleModes timescaleMode = TimescaleModes.Scaled, bool stop = false, bool restore = false) { if (!CheckEventAllowed(channelData)) return; if (stop) { Stop(); return; } if (restore) { ResetTargetValues(); return; } // 创建新实例并加入列表 MMCinemachineRotationShakeInstance newShake = new MMCinemachineRotationShakeInstance( duration, rotationAmplitude, // 这里的 Amplitude 已经是外部传入的基准值,Intensity 在 Update 中计算 shakeCurve, feedbacksIntensity, timescaleMode ); _activeShakes.Add(newShake); Play(source); } public override void Stop() { _activeShakes.Clear(); ApplyRotation(0f, 0f, 0f); } protected override void ResetTargetValues() { base.ResetTargetValues(); Stop(); } public override void StartListening() { base.StartListening(); MMCinemachineRotationShakeEvent.Register(OnMMCinemachineRotationShakeEvent); } public override void StopListening() { base.StopListening(); MMCinemachineRotationShakeEvent.Unregister(OnMMCinemachineRotationShakeEvent); } } }