240 lines
8.9 KiB
C#
240 lines
8.9 KiB
C#
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Cinemachine Rotation Shaker (Additive / Mixing Support)
|
||
/// X/Y applied to FollowTarget, Z applied to Camera Dutch.
|
||
/// 支持多个 Feedback 同时叠加震动。
|
||
/// </summary>
|
||
[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<MMCinemachineRotationShakeInstance> _activeShakes = new List<MMCinemachineRotationShakeInstance>();
|
||
|
||
protected override void Initialization()
|
||
{
|
||
// 不调用 base.Initialization() 以完全接管 Update 逻辑
|
||
#if MM_CINEMACHINE
|
||
_targetCamera = GetComponent<CinemachineVirtualCamera>();
|
||
#elif MM_CINEMACHINE3
|
||
_targetCamera = GetComponent<CinemachineCamera>();
|
||
#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);
|
||
}
|
||
}
|
||
} |