Files
Cielonos/Assets/Scripts/MainGame/Effects/Feedbacks/Shakers/Cinemachine/CameraFovShaker.cs

152 lines
4.5 KiB
C#
Raw Normal View History

2026-04-18 13:57:19 -04:00
using System.Collections.Generic;
using SLSUtilities.Feedback;
using Unity.Cinemachine;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 单个FOV震动实例的运行时状态。
/// </summary>
public class CameraFovShakeInstance : ShakeInstanceBase
{
public FloatCurveChannel fovCurve;
public CameraFovShakeInstance(FeedbackTimeSettings timeSettings, IFeedbackTimeProvider timeProvider,
FloatCurveChannel fovCurve, float duration) : base(timeSettings, timeProvider, duration)
{
this.fovCurve = fovCurve;
}
}
/// <summary>
/// 摄像机视野角(FOV)震动事件,用于解耦 Action 与 Shaker。
/// </summary>
public struct CameraFovShakeEvent
{
private static event ShakeDelegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void RuntimeInitialization() { OnEvent = null; }
public delegate void ShakeDelegate(
FeedbackContext feedbackContext,
FloatCurveChannel fovCurve,
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 fovCurve,
bool stop = false)
{
OnEvent?.Invoke(feedbackContext, fovCurve, stop);
}
}
/// <summary>
/// CinemachineCamera FOV 震动聚合器。挂载于 CinemachineCamera 上,
/// 监听 CameraFovShakeEvent叠加驱动 Lens.FieldOfView。
/// </summary>
[AddComponentMenu("Cielonos/Feedback Shakers/Camera FOV Shaker")]
[RequireComponent(typeof(CinemachineCamera))]
public class CameraFovShaker : MonoBehaviour
{
private CinemachineCamera _camera;
2026-04-28 15:46:32 -04:00
public float initialFov;
2026-04-18 13:57:19 -04:00
private readonly List<CameraFovShakeInstance> _activeShakes = new List<CameraFovShakeInstance>();
private void Awake()
{
_camera = GetComponent<CinemachineCamera>();
2026-04-28 15:46:32 -04:00
initialFov = _camera.Lens.FieldOfView;
2026-04-18 13:57:19 -04:00
}
private void OnEnable()
{
CameraFovShakeEvent.Register(OnShakeEvent);
}
private void OnDisable()
{
CameraFovShakeEvent.Unregister(OnShakeEvent);
StopAll();
}
private void Update()
{
if (_camera == null) return;
if (_activeShakes.Count == 0)
{
2026-04-28 15:46:32 -04:00
SetFov(initialFov);
2026-04-18 13:57:19 -04:00
return;
}
float additiveFov = 0f;
float absoluteFov = 0f;
bool hasAbsolute = false;
for (int i = _activeShakes.Count - 1; i >= 0; i--)
{
CameraFovShakeInstance shake = _activeShakes[i];
shake.Tick();
float normalizedTime = shake.timer / shake.duration;
if (shake.fovCurve.relativeToInitial)
{
additiveFov += shake.fovCurve.Evaluate(normalizedTime);
}
else
{
absoluteFov = shake.fovCurve.Evaluate(normalizedTime);
hasAbsolute = true;
}
if (shake.IsFinished)
{
_activeShakes.RemoveAt(i);
}
}
2026-04-28 15:46:32 -04:00
float finalFov = hasAbsolute ? absoluteFov : initialFov + additiveFov;
2026-04-18 13:57:19 -04:00
SetFov(finalFov);
}
private void OnShakeEvent(
FeedbackContext feedbackContext,
FloatCurveChannel fovCurve,
bool stop)
{
if (stop) { StopAll(); return; }
var instance = new CameraFovShakeInstance(
feedbackContext.timeSettings,
feedbackContext.player.TimeProvider,
fovCurve,
feedbackContext.duration
);
_activeShakes.Add(instance);
}
private void SetFov(float fov)
{
LensSettings lens = _camera.Lens;
lens.FieldOfView = fov;
_camera.Lens = lens;
}
private void StopAll()
{
_activeShakes.Clear();
if (_camera != null)
{
2026-04-28 15:46:32 -04:00
SetFov(initialFov);
2026-04-18 13:57:19 -04:00
}
}
}
}