using System.Collections.Generic; using SLSUtilities.Feedback; using Unity.Cinemachine; using UnityEngine; namespace Cielonos.MainGame.Effects.Feedback { public class CameraOrbitShakeInstance : ShakeInstanceBase { // Yaw 设置 public bool enableYaw; public CameraOrbitAction.OrbitMode yawMode; public FloatCurveChannel horizontalCurve; public float startHorizontalValue; public float worldEndHorizontalValue; public AnimationCurve yawEaseCurve; // Pitch 设置 public bool enablePitch; public CameraOrbitAction.OrbitMode pitchMode; public FloatCurveChannel verticalCurve; public float startVerticalValue; public float worldEndVerticalValue; public AnimationCurve pitchEaseCurve; public CameraOrbitShakeInstance(FeedbackTimeSettings timeSettings, IFeedbackTimeProvider timeProvider, CameraOrbitAction action, float startH, float startV, float worldEndH, float worldEndV, float duration) : base(timeSettings, timeProvider, duration) { this.enableYaw = action.enableYaw; this.yawMode = action.yawMode; this.horizontalCurve = action.horizontalCurve; this.startHorizontalValue = startH; this.worldEndHorizontalValue = worldEndH; this.yawEaseCurve = action.yawEaseCurve; this.enablePitch = action.enablePitch; this.pitchMode = action.pitchMode; this.verticalCurve = action.verticalCurve; this.startVerticalValue = startV; this.worldEndVerticalValue = worldEndV; this.pitchEaseCurve = action.pitchEaseCurve; } /// /// 在 TargetValue 模式下,根据归一化时间计算当前水平角度。 /// public float EvaluateHorizontal(float normalizedTime) { if (yawMode == CameraOrbitAction.OrbitMode.Additive) { return horizontalCurve.Evaluate(normalizedTime); } float t = yawEaseCurve != null ? yawEaseCurve.Evaluate(Mathf.Clamp01(normalizedTime)) : normalizedTime; return Mathf.LerpUnclamped(startHorizontalValue, worldEndHorizontalValue, t); } /// /// 在 TargetValue 模式下,根据归一化时间计算当前垂直角度。 /// public float EvaluateVertical(float normalizedTime) { if (pitchMode == CameraOrbitAction.OrbitMode.Additive) { return verticalCurve.Evaluate(normalizedTime); } float t = pitchEaseCurve != null ? pitchEaseCurve.Evaluate(Mathf.Clamp01(normalizedTime)) : normalizedTime; return Mathf.LerpUnclamped(startVerticalValue, worldEndVerticalValue, t); } } public struct CameraOrbitEvent { private static event ShakeDelegate OnEvent; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; } public delegate void ShakeDelegate( FeedbackContext feedbackContext, CameraOrbitAction action, bool stop ); public static void Register(ShakeDelegate callback) { OnEvent += callback; } public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; } public static void Trigger( FeedbackContext feedbackContext, CameraOrbitAction action, bool stop = false) { OnEvent?.Invoke(feedbackContext, action, stop); } } [AddComponentMenu("Cielonos/Feedback Shakers/Camera Orbit Shaker")] [RequireComponent(typeof(CinemachineCamera))] [RequireComponent(typeof(CinemachineOrbitalFollow))] public class CameraOrbitShaker : MonoBehaviour { private CinemachineCamera _camera; private CinemachineOrbitalFollow _orbitalFollow; private CinemachineInputAxisController _inputController; [Tooltip("目标参考的 Transform。当 CameraOrbitAction 处于 TargetValue 模式时,将以此为基准转换 endHorizontalValue 为世界空间角度。" + "如果为空,则默认使用 MainGameManager.Player.transform,若仍为空则回退到相机的 Follow 目标。")] [SerializeField] private Transform targetReference; private float _baseHorizontalValue; private float _baseVerticalValue; private bool _hasBaseValuesCaptured; private readonly List _activeShakes = new List(); public float CurrentHorizontalOffset { get; private set; } public float CurrentVerticalOffset { get; private set; } public bool HasActiveShakes => _activeShakes.Count > 0; private void Awake() { _camera = GetComponent(); _orbitalFollow = GetComponent(); _inputController = GetComponent(); } private void OnEnable() { CameraOrbitEvent.Register(OnShakeEvent); } private void OnDisable() { CameraOrbitEvent.Unregister(OnShakeEvent); StopAll(); } private void Update() { if (_orbitalFollow == null) return; if (_activeShakes.Count == 0) { CurrentHorizontalOffset = 0f; CurrentVerticalOffset = 0f; if (_hasBaseValuesCaptured) { // 所有 Shake 结束:恢复玩家输入控制器 if (_inputController != null && !_inputController.enabled) { _inputController.enabled = true; } _hasBaseValuesCaptured = false; } return; } // 首次触发时捕获当前相机角度作为基准 if (!_hasBaseValuesCaptured) { _baseHorizontalValue = _orbitalFollow.HorizontalAxis.Value; _baseVerticalValue = _orbitalFollow.VerticalAxis.Value; _hasBaseValuesCaptured = true; } // 区分模式与轨道进行独立计算 float finalHorizontal = _baseHorizontalValue; float finalVertical = _baseVerticalValue; for (int i = _activeShakes.Count - 1; i >= 0; i--) { CameraOrbitShakeInstance shake = _activeShakes[i]; shake.Tick(); float normalizedTime = shake.timer / shake.duration; // 独立水平轴计算 if (shake.enableYaw) { if (shake.yawMode == CameraOrbitAction.OrbitMode.Additive) { finalHorizontal += shake.EvaluateHorizontal(normalizedTime); } else { finalHorizontal = shake.EvaluateHorizontal(normalizedTime); } } // 独立垂直轴计算 if (shake.enablePitch) { if (shake.pitchMode == CameraOrbitAction.OrbitMode.Additive) { finalVertical += shake.EvaluateVertical(normalizedTime); } else { finalVertical = shake.EvaluateVertical(normalizedTime); } } if (shake.IsFinished) { _activeShakes.RemoveAt(i); } } // 管理玩家输入控制器状态(有任何活跃 Orbit Shake 时必定禁用输入) if (_inputController != null && _inputController.enabled) { _inputController.enabled = false; } // 计算提供给锁锁相机的当前偏差量 CurrentHorizontalOffset = finalHorizontal - _baseHorizontalValue; CurrentVerticalOffset = finalVertical - _baseVerticalValue; // 检查当前是否在使用锁定相机的硬锁状态,若是,则由 LockTargetSubmodule 接管赋值,避免冲突 bool isLocking = MainGameManager.Player != null && MainGameManager.Player.viewSc.lockTargetModule != null && MainGameManager.Player.viewSc.lockTargetModule.isUsingLockTargetCamera && gameObject == MainGameManager.Player.viewSc.lockingTargetCamera.gameObject; if (!isLocking) { // 非锁定状态下直接赋值 _orbitalFollow.HorizontalAxis.Value = finalHorizontal; _orbitalFollow.VerticalAxis.Value = finalVertical; } } private void OnShakeEvent( FeedbackContext feedbackContext, CameraOrbitAction action, bool stop) { if (stop) { StopAll(); return; } // 仅在当前组件附加的虚拟相机是当前激活相机时,才响应 Orbit 动作,防止后台相机数据被污染 bool isActiveCamera = MainGameManager.Player != null && MainGameManager.Player.viewSc.currentCamera != null && gameObject == MainGameManager.Player.viewSc.currentCamera.gameObject; if (!isActiveCamera) return; // 检查当前是否在使用锁定相机的硬锁状态 bool isLocking = MainGameManager.Player != null && MainGameManager.Player.viewSc.lockTargetModule != null && MainGameManager.Player.viewSc.lockTargetModule.isUsingLockTargetCamera && gameObject == MainGameManager.Player.viewSc.lockingTargetCamera.gameObject; // 根据当前相机状态与配置决定是否生效 if (isLocking && !action.enableInLockTarget) return; if (!isLocking && !action.enableInFreeLook) return; // 获取当前轴值作为 TargetValue 模式的起始值 float currentH = _orbitalFollow != null ? _orbitalFollow.HorizontalAxis.Value : 0f; float currentV = _orbitalFollow != null ? _orbitalFollow.VerticalAxis.Value : 0f; // 解析水平目标值与垂直目标值 float worldEndH = currentH; float worldEndV = action.endVerticalValue; // 水平 (Yaw) 解析 if (action.enableYaw && action.yawMode == CameraOrbitAction.OrbitMode.TargetValue) { Transform reference = targetReference != null ? targetReference : (MainGameManager.Player != null ? MainGameManager.Player.transform : _camera?.Follow); float playerYaw = reference != null ? reference.eulerAngles.y : 0f; float targetAngle = playerYaw + action.endHorizontalValue; if (action.shortestPath) { worldEndH = currentH + Mathf.DeltaAngle(currentH, targetAngle); } else { worldEndH = targetAngle; } } // 垂直 (Pitch) 解析 if (action.enablePitch && action.pitchMode == CameraOrbitAction.OrbitMode.TargetValue) { if (isLocking) { // 硬锁相机下,垂直仰角(Pitch)的 TargetValue 是相对于触发时的基准仰角的相对偏移 worldEndV = currentV + action.endVerticalValue; } else { worldEndV = action.endVerticalValue; } } var instance = new CameraOrbitShakeInstance( feedbackContext.timeSettings, feedbackContext.player.TimeProvider, action, currentH, currentV, worldEndH, worldEndV, feedbackContext.duration ); _activeShakes.Add(instance); } private void StopAll() { _activeShakes.Clear(); if (_hasBaseValuesCaptured && _orbitalFollow != null) { _orbitalFollow.HorizontalAxis.Value = _baseHorizontalValue; _orbitalFollow.VerticalAxis.Value = _baseVerticalValue; } if (_inputController != null) { _inputController.enabled = true; } _hasBaseValuesCaptured = false; } } }