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, bool modifyExposure, FloatCurveChannel exposureCurve, bool modifyContrast, FloatCurveChannel contrastCurve, bool modifySaturation, FloatCurveChannel saturationCurve, bool modifyHue, FloatCurveChannel hueCurve, bool modifyColorFilter, 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, bool modifyExposure = false, FloatCurveChannel exposureCurve = default, bool modifyContrast = false, FloatCurveChannel contrastCurve = default, bool modifySaturation = false, FloatCurveChannel saturationCurve = default, bool modifyHue = false, FloatCurveChannel hueCurve = default, bool modifyColorFilter = false, ColorCurveChannel colorFilterCurve = default, bool stop = false) { OnEvent?.Invoke( feedbackContext, modifyExposure, exposureCurve, modifyContrast, contrastCurve, modifySaturation, saturationCurve, modifyHue, hueCurve, modifyColorFilter, colorFilterCurve, stop ); } } /// /// Anime ACES 震动实例。 /// public class AnimeACESShakeInstance : ShakeInstanceBase { public readonly bool modifyExposure; public readonly FloatCurveChannel exposureCurve; public readonly bool modifyContrast; public readonly FloatCurveChannel contrastCurve; public readonly bool modifySaturation; public readonly FloatCurveChannel saturationCurve; public readonly bool modifyHue; public readonly FloatCurveChannel hueCurve; public readonly bool modifyColorFilter; public readonly ColorCurveChannel colorFilterCurve; public AnimeACESShakeInstance( FeedbackContext feedbackContext, bool modifyExposure, FloatCurveChannel exposureCurve, bool modifyContrast, FloatCurveChannel contrastCurve, bool modifySaturation, FloatCurveChannel saturationCurve, bool modifyHue, FloatCurveChannel hueCurve, bool modifyColorFilter, ColorCurveChannel colorFilterCurve) : base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration) { this.modifyExposure = modifyExposure; this.modifyContrast = modifyContrast; this.modifySaturation = modifySaturation; this.modifyHue = modifyHue; this.modifyColorFilter = modifyColorFilter; this.exposureCurve = exposureCurve; this.contrastCurve = contrastCurve; this.saturationCurve = saturationCurve; this.hueCurve = hueCurve; this.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.Tick(); float normalizedTime = shake.timer / shake.duration; // Exposure if (shake.modifyExposure) { additiveExposure += shake.exposureCurve.Evaluate(normalizedTime); Debug.Log($"Exposure shake: {additiveExposure}"); } // Contrast if (shake.modifyContrast) { additiveContrast += shake.contrastCurve.Evaluate(normalizedTime); } // Saturation if (shake.modifySaturation) { additiveSaturation += shake.saturationCurve.Evaluate(normalizedTime); } // Hue if (shake.modifyHue) { additiveHue += shake.hueCurve.Evaluate(normalizedTime); } // Color Filter if (shake.modifyColorFilter) { 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, bool modifyExposure, FloatCurveChannel exposureCurve, bool modifyContrast, FloatCurveChannel contrastCurve, bool modifySaturation, FloatCurveChannel saturationCurve, bool modifyHue, FloatCurveChannel hueCurve, bool modifyColorFilter, ColorCurveChannel colorFilterCurve, bool stop) { if (stop) { StopAll(); return; } if (!_resolved) _resolved = TryResolve(); if (!_resolved) return; var instance = new AnimeACESShakeInstance( feedbackContext, modifyExposure, exposureCurve, modifyContrast, contrastCurve, modifySaturation, saturationCurve, modifyHue, hueCurve, modifyColorFilter, 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(); } } }