Files
Cielonos/Assets/Scripts/MainGame/Effects/Feedbacks/Shakers/Cinemachine/CameraDistanceShaker.cs
SoulliesOfficial 9a9e48f8a5
2026-06-27 12:52:03 -04:00

154 lines
4.9 KiB
C#

using System.Collections.Generic;
using SLSUtilities.Feedback;
using Unity.Cinemachine;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
public class CameraDistanceShakeInstance : ShakeInstanceBase
{
public FloatCurveChannel distanceCurve;
public CameraDistanceShakeInstance(FeedbackTimeSettings timeSettings, IFeedbackTimeProvider timeProvider,
FloatCurveChannel distanceCurve, float duration) : base(timeSettings, timeProvider, duration)
{
this.distanceCurve = distanceCurve;
}
}
public struct CameraDistanceShakeEvent
{
private static event ShakeDelegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void RuntimeInitialization() { OnEvent = null; }
public delegate void ShakeDelegate(
FeedbackContext feedbackContext,
FloatCurveChannel distanceCurve,
bool stop
);
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
public static void Trigger(
FeedbackContext feedbackContext,
FloatCurveChannel distanceCurve,
bool stop = false)
{
OnEvent?.Invoke(feedbackContext, distanceCurve, stop);
}
}
[AddComponentMenu("Cielonos/Feedback Shakers/Camera Distance Shaker")]
[RequireComponent(typeof(CinemachineCamera))]
[RequireComponent(typeof(CinemachineOrbitalFollow))]
public class CameraDistanceShaker : MonoBehaviour
{
private CinemachineCamera _camera;
private CinemachineOrbitalFollow _orbitalFollow;
public float initialDistance;
[Tooltip("防止因为同类 Feedback 叠加导致距离过小")]
public float minDistance = 1f;
[Tooltip("防止因为同类 Feedback 叠加导致距离过大")]
public float maxDistance = 30f;
private readonly List<CameraDistanceShakeInstance> _activeShakes = new List<CameraDistanceShakeInstance>();
private void Awake()
{
_camera = GetComponent<CinemachineCamera>();
_orbitalFollow = GetComponent<CinemachineOrbitalFollow>();
initialDistance = _orbitalFollow.Radius;
}
private void OnEnable()
{
CameraDistanceShakeEvent.Register(OnShakeEvent);
}
private void OnDisable()
{
CameraDistanceShakeEvent.Unregister(OnShakeEvent);
StopAll();
}
private void Update()
{
if (_orbitalFollow == null) return;
if (_activeShakes.Count == 0)
{
SetDistance(initialDistance);
return;
}
float additiveDistance = 0f;
float absoluteDistance = 0f;
bool hasAbsolute = false;
for (int i = _activeShakes.Count - 1; i >= 0; i--)
{
CameraDistanceShakeInstance shake = _activeShakes[i];
shake.Tick();
float normalizedTime = shake.timer / shake.duration;
if (shake.distanceCurve.relativeToInitial)
{
additiveDistance += shake.distanceCurve.Evaluate(normalizedTime);
}
else
{
absoluteDistance = shake.distanceCurve.Evaluate(normalizedTime);
hasAbsolute = true;
}
if (shake.IsFinished)
{
_activeShakes.RemoveAt(i);
}
}
float finalDistance = hasAbsolute ? absoluteDistance : initialDistance + additiveDistance;
// 核心防御:钳制最终距离,防止多技能/反馈叠加导致的距离暴走
finalDistance = Mathf.Clamp(finalDistance, minDistance, maxDistance);
SetDistance(finalDistance);
}
private void OnShakeEvent(
FeedbackContext feedbackContext,
FloatCurveChannel distanceCurve,
bool stop)
{
if (stop) { StopAll(); return; }
var instance = new CameraDistanceShakeInstance(
feedbackContext.timeSettings,
feedbackContext.player.TimeProvider,
distanceCurve,
feedbackContext.duration
);
_activeShakes.Add(instance);
}
private void SetDistance(float distance)
{
_orbitalFollow.Radius = distance;
}
private void StopAll()
{
_activeShakes.Clear();
if (_orbitalFollow != null)
{
SetDistance(initialDistance);
}
}
}
}