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);
}
}
}