using System.Collections.Generic; using SLSUtilities.Feedback; using SLSUtilities.Rendering.PostProcessing; using UnityEngine; namespace Cielonos.MainGame.Effects.Feedback { /// /// Anime ACES 震动事件。 /// public struct AnimeACESShakeEvent { private static event ShakeDelegate OnEvent; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void RuntimeInitialization() { OnEvent = null; } public delegate void ShakeDelegate( FeedbackContext feedbackContext, FloatCurveChannel exposureCurve, FloatCurveChannel contrastCurve, FloatCurveChannel saturationCurve, FloatCurveChannel hueCurve, ColorCurveChannel colorFilterCurve, 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 exposureCurve = default, FloatCurveChannel contrastCurve = default, FloatCurveChannel saturationCurve = default, FloatCurveChannel hueCurve = default, ColorCurveChannel colorFilterCurve = default, bool stop = false) { OnEvent?.Invoke(feedbackContext, exposureCurve, contrastCurve, saturationCurve, hueCurve, colorFilterCurve, stop); } } /// /// Anime ACES 震动实例。 /// public class AnimeACESShakeInstance : ShakeInstanceBase { public readonly FloatCurveChannel ExposureCurve; public readonly FloatCurveChannel ContrastCurve; public readonly FloatCurveChannel SaturationCurve; public readonly FloatCurveChannel HueCurve; public readonly ColorCurveChannel ColorFilterCurve; public AnimeACESShakeInstance( FeedbackContext feedbackContext, FloatCurveChannel exposureCurve, FloatCurveChannel contrastCurve, FloatCurveChannel saturationCurve, FloatCurveChannel hueCurve, ColorCurveChannel colorFilterCurve) : base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration) { ExposureCurve = exposureCurve; ContrastCurve = contrastCurve; SaturationCurve = saturationCurve; HueCurve = hueCurve; ColorFilterCurve = colorFilterCurve; } } /// /// AnimeACES 的震动聚合器。 /// [AddComponentMenu("SLS Utilities/Feedback Shakers/Anime ACES Shaker")] public class AnimeACESShaker : MonoBehaviour { private AnimeACES _component; private float _initExposure; private float _initContrast; private float _initSaturation; private float _initHue; private Color _initColorFilter; private bool _resolved; private readonly List _activeShakes = new List(); private void Awake() { _resolved = TryResolve(); } private void OnEnable() { AnimeACESShakeEvent.Register(OnShakeEvent); } private void OnDisable() { AnimeACESShakeEvent.Unregister(OnShakeEvent); StopAll(); } private void Update() { if (!_resolved || _activeShakes.Count == 0) return; float additiveExposure = 0f; float additiveContrast = 0f; float additiveSaturation = 0f; float additiveHue = 0f; Color colorFilterAccum = Color.white; bool hasColorFilter = false; for (int i = _activeShakes.Count - 1; i >= 0; i--) { AnimeACESShakeInstance shake = _activeShakes[i]; shake.timer += shake.timeProvider.GetDeltaTime(shake.timeSettings); float normalizedTime = shake.timer / shake.duration; // Exposure if (shake.ExposureCurve.active) { additiveExposure += shake.ExposureCurve.Evaluate(normalizedTime); } // Contrast if (shake.ContrastCurve.active) { additiveContrast += shake.ContrastCurve.Evaluate(normalizedTime); } // Saturation if (shake.SaturationCurve.active) { additiveSaturation += shake.SaturationCurve.Evaluate(normalizedTime); } // Hue if (shake.HueCurve.active) { additiveHue += shake.HueCurve.Evaluate(normalizedTime); } // Color Filter if (shake.ColorFilterCurve.active) { colorFilterAccum = shake.ColorFilterCurve.Evaluate(normalizedTime); hasColorFilter = true; } if (shake.IsFinished) { _activeShakes.RemoveAt(i); } } _component.exposure.value = _initExposure + additiveExposure; _component.contrast.value = _initContrast + additiveContrast; _component.saturation.value = _initSaturation + additiveSaturation; _component.huePreservation.value = _initHue + additiveHue; if (hasColorFilter) _component.colorFilter.value = colorFilterAccum; if (_activeShakes.Count == 0) { Restore(); } } private void OnShakeEvent( FeedbackContext feedbackContext, FloatCurveChannel exposureCurve, FloatCurveChannel contrastCurve, FloatCurveChannel saturationCurve, FloatCurveChannel hueCurve, ColorCurveChannel colorFilterCurve, bool stop) { if (stop) { StopAll(); return; } if (!_resolved) _resolved = TryResolve(); if (!_resolved) return; var instance = new AnimeACESShakeInstance( feedbackContext, exposureCurve, contrastCurve, saturationCurve, hueCurve, colorFilterCurve ); _activeShakes.Add(instance); } private bool TryResolve() { if (_component != null) return true; if (PostProcessingManager.Instance == null) return false; if (!PostProcessingManager.Instance.GetVolumeComponent(out _component)) return false; _initExposure = _component.exposure.value; _initContrast = _component.contrast.value; _initSaturation = _component.saturation.value; _initHue = _component.huePreservation.value; _initColorFilter = _component.colorFilter.value; return true; } private void Restore() { if (!_resolved) return; _component.exposure.value = _initExposure; _component.contrast.value = _initContrast; _component.saturation.value = _initSaturation; _component.huePreservation.value = _initHue; _component.colorFilter.value = _initColorFilter; } private void StopAll() { _activeShakes.Clear(); Restore(); } } }