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); } } } }