Files
Cielonos/Assets/Scripts/MainGame/Effects/Feedbacks/Shakers/Cinemachine/CameraOrbitShaker.cs
SoulliesOfficial ddd387ef35 做不出来
2026-06-30 01:48:58 -04:00

327 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
/// <summary>
/// 在 TargetValue 模式下,根据归一化时间计算当前水平角度。
/// </summary>
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);
}
/// <summary>
/// 在 TargetValue 模式下,根据归一化时间计算当前垂直角度。
/// </summary>
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<CameraOrbitShakeInstance> _activeShakes = new List<CameraOrbitShakeInstance>();
public float CurrentHorizontalOffset { get; private set; }
public float CurrentVerticalOffset { get; private set; }
public bool HasActiveShakes => _activeShakes.Count > 0;
private void Awake()
{
_camera = GetComponent<CinemachineCamera>();
_orbitalFollow = GetComponent<CinemachineOrbitalFollow>();
_inputController = GetComponent<CinemachineInputAxisController>();
}
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;
}
}
}