using System.Collections.Generic;
using SLSUtilities.Feedback;
using Unity.Cinemachine;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
///
/// 单个FOV震动实例的运行时状态。
///
public class CameraFovShakeInstance : ShakeInstanceBase
{
public FloatCurveChannel fovCurve;
public CameraFovShakeInstance(FeedbackTimeSettings timeSettings, IFeedbackTimeProvider timeProvider,
FloatCurveChannel fovCurve, float duration) : base(timeSettings, timeProvider, duration)
{
this.fovCurve = fovCurve;
}
}
///
/// 摄像机视野角(FOV)震动事件,用于解耦 Action 与 Shaker。
///
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);
}
}
///
/// CinemachineCamera FOV 震动聚合器。挂载于 CinemachineCamera 上,
/// 监听 CameraFovShakeEvent,叠加驱动 Lens.FieldOfView。
///
[AddComponentMenu("Cielonos/Feedback Shakers/Camera FOV Shaker")]
[RequireComponent(typeof(CinemachineCamera))]
public class CameraFovShaker : MonoBehaviour
{
private CinemachineCamera _camera;
public float initialFov;
private readonly List _activeShakes = new List();
private void Awake()
{
_camera = GetComponent();
initialFov = _camera.Lens.FieldOfView;
}
private void OnEnable()
{
CameraFovShakeEvent.Register(OnShakeEvent);
}
private void OnDisable()
{
CameraFovShakeEvent.Unregister(OnShakeEvent);
StopAll();
}
private void Update()
{
if (_camera == null) return;
if (_activeShakes.Count == 0)
{
SetFov(initialFov);
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);
}
}
float finalFov = hasAbsolute ? absoluteFov : initialFov + additiveFov;
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)
{
SetFov(initialFov);
}
}
}
}