From 59d4715d826e2bf368c08b7e46f5abee5d9d8e76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:13:56 +0000 Subject: [PATCH 1/3] Initial plan From 3e8701de631d70a7112e31c13fa4da31b3dca948 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:31:12 +0000 Subject: [PATCH 2/3] Add configurable easing_curve property to all visual effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hard-coded envelope/easing logic in every effect runner with a configurable AnimationCurve property named easing_curve. - Add EntityCurveExtensions.cs with GetAnimationCurve extension method - Add GetAnimationCurve stub to Entity in CiGameTypeStubs.cs - For all 13 effects: add easing_curve to template properties, read it in TrySchedule, pass to runner Initialize, store in _easingCurve field, and use _easingCurve.Evaluate(progress) in LateUpdate - Default curves exactly replicate the original hard-coded behaviour: - CameraShake: linear decay (0,1)→(1,0) tangents -1 - CameraTilt: sine bell via keyframes at 0/0.5/1 with π tangents - ColorTint/Fog/Hsl/Sepia/Vignette: 20%/80% trapezoid slopes ±5 - Letterbox/PixelGrid/Scanlines/ScreenNoise: 15%/85% trapezoid slopes ±20/3 - ZoomIn/ZoomOut: SmoothStep 20%/80% trapezoid (all tangents 0) - ZoomPulse: triangle peaking at 30%, slopes 10/3 and -10/7 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Core/EntityCurveExtensions.cs | 22 ++++++++++++++++ .../Effects/CameraShake/CameraShakeEffect.cs | 17 ++++++++++--- .../Effects/CameraTilt/CameraTiltEffect.cs | 20 +++++++++++---- .../Effects/ColorTint/ColorTintEffect.cs | 25 +++++++++++-------- BopVisualEffects/Effects/Fog/FogEffect.cs | 25 +++++++++++-------- BopVisualEffects/Effects/Hsl/HslEffect.cs | 25 +++++++++++-------- .../Effects/Letterbox/LetterboxEffect.cs | 25 +++++++++++-------- .../Effects/PixelGrid/PixelGridEffect.cs | 25 +++++++++++-------- .../Effects/Scanlines/ScanlinesEffect.cs | 25 +++++++++++-------- .../Effects/ScreenNoise/ScreenNoiseEffect.cs | 25 +++++++++++-------- BopVisualEffects/Effects/Sepia/SepiaEffect.cs | 25 +++++++++++-------- .../Effects/Vignette/VignetteEffect.cs | 25 +++++++++++-------- .../Effects/ZoomIn/ZoomInEffect.cs | 25 +++++++++++-------- .../Effects/ZoomOut/ZoomOutEffect.cs | 25 +++++++++++-------- .../Effects/ZoomPulse/ZoomPulseEffect.cs | 20 ++++++++++----- BopVisualEffects/Stubs/CiGameTypeStubs.cs | 12 +++++++++ 16 files changed, 241 insertions(+), 125 deletions(-) create mode 100644 BopVisualEffects/Core/EntityCurveExtensions.cs diff --git a/BopVisualEffects/Core/EntityCurveExtensions.cs b/BopVisualEffects/Core/EntityCurveExtensions.cs new file mode 100644 index 0000000..51f32dd --- /dev/null +++ b/BopVisualEffects/Core/EntityCurveExtensions.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +namespace BopVisualEffects.Core; + +/// +/// Extension helper for reading values from properties. +/// +internal static class EntityCurveExtensions +{ + /// + /// Returns the stored under , or + /// when the key is absent or its value is not an + /// . + /// + internal static AnimationCurve GetAnimationCurve(this Entity entity, string key, AnimationCurve fallback) + { + if (entity.properties.TryGetValue(key, out var value) && value is AnimationCurve curve) + return curve; + + return fallback; + } +} diff --git a/BopVisualEffects/Effects/CameraShake/CameraShakeEffect.cs b/BopVisualEffects/Effects/CameraShake/CameraShakeEffect.cs index 55a33c2..a6edee7 100644 --- a/BopVisualEffects/Effects/CameraShake/CameraShakeEffect.cs +++ b/BopVisualEffects/Effects/CameraShake/CameraShakeEffect.cs @@ -32,7 +32,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) properties = new Dictionary { ["amplitude"] = 0.1f, - ["frequency"] = 18.0f + ["frequency"] = 18.0f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -44,6 +45,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var durationBeats = Mathf.Max(0.01f, entity.length); var amplitude = entity.GetFloat("amplitude"); var frequency = entity.GetFloat("frequency"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -54,10 +56,15 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, amplitude, frequency)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, amplitude, frequency, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 1f, 0f, -1f), + new Keyframe(1f, 0f, -1f, 0f) + ); + private sealed class CameraShakeRunner : MonoBehaviour { private bool _initialized; @@ -69,11 +76,12 @@ private sealed class CameraShakeRunner : MonoBehaviour private JukeboxScript? _jukebox; private Transform? _targetTransform; private Vector3 _initialLocalPosition; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float amplitude, float frequency) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float amplitude, float frequency, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -81,6 +89,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _endBeat = endBeat; _amplitude = Mathf.Max(0f, amplitude); _frequency = Mathf.Max(0.1f, frequency); + _easingCurve = easingCurve; InitializeTargetCamera(); } @@ -116,7 +125,7 @@ private void LateUpdate() } var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - var envelope = 1f - progress; + var envelope = _easingCurve.Evaluate(progress); if (envelope <= 0f) { Stop(); diff --git a/BopVisualEffects/Effects/CameraTilt/CameraTiltEffect.cs b/BopVisualEffects/Effects/CameraTilt/CameraTiltEffect.cs index 05a48b9..d22a991 100644 --- a/BopVisualEffects/Effects/CameraTilt/CameraTiltEffect.cs +++ b/BopVisualEffects/Effects/CameraTilt/CameraTiltEffect.cs @@ -31,7 +31,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["angle"] = 5.0f + ["angle"] = 5.0f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -42,6 +43,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var angle = entity.GetFloat("angle"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -52,10 +54,16 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, angle)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, angle, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, Mathf.PI), + new Keyframe(0.5f, 1f, 0f, 0f), + new Keyframe(1f, 0f, -Mathf.PI, 0f) + ); + private sealed class CameraTiltRunner : MonoBehaviour { private bool _initialized; @@ -66,17 +74,19 @@ private sealed class CameraTiltRunner : MonoBehaviour private JukeboxScript? _jukebox; private Transform? _targetTransform; private Quaternion _initialLocalRotation; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float angle) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float angle, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _angle = angle; + _easingCurve = easingCurve; InitializeTargetCamera(); } @@ -111,9 +121,9 @@ private void LateUpdate() return; } - // Swing tilt: rotate in one direction and back (full sine wave over the duration). + // Swing tilt: rotate in one direction and back (configurable curve over the duration). var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - var tiltAngle = _angle * Mathf.Sin(progress * Mathf.PI); + var tiltAngle = _angle * _easingCurve.Evaluate(progress); _targetTransform!.localRotation = _initialLocalRotation * Quaternion.Euler(0f, 0f, tiltAngle); } diff --git a/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs b/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs index c3c3216..8e685b2 100644 --- a/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs +++ b/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs @@ -35,7 +35,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) ["r"] = 1.0f, ["g"] = 0.0f, ["b"] = 0.0f, - ["alpha"] = 0.25f + ["alpha"] = 0.25f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -49,6 +50,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var g = entity.GetFloat("g"); var b = entity.GetFloat("b"); var alpha = entity.GetFloat("alpha"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -59,10 +61,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, r, g, b, alpha)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, r, g, b, alpha, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 5f), + new Keyframe(0.2f, 1f, 5f, 0f), + new Keyframe(0.8f, 1f, 0f, -5f), + new Keyframe(1f, 0f, -5f, 0f) + ); + private sealed class ColorTintRunner : MonoBehaviour { private bool _initialized; @@ -75,11 +84,12 @@ private sealed class ColorTintRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private ColorTintOverlay? _overlay; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float r, float g, float b, float alpha) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float r, float g, float b, float alpha, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -89,6 +99,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _g = Mathf.Clamp01(g); _b = Mathf.Clamp01(b); _maxAlpha = Mathf.Clamp01(alpha); + _easingCurve = easingCurve; InitializeOverlay(); } @@ -125,13 +136,7 @@ private void LateUpdate() // Fade in over first 20%, hold, fade out over last 20%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.2f) - envelope = Mathf.InverseLerp(0f, 0.2f, progress); - else if (progress > 0.8f) - envelope = 1f - Mathf.InverseLerp(0.8f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_overlay != null) _overlay.SetColor(_r, _g, _b, _maxAlpha * envelope); diff --git a/BopVisualEffects/Effects/Fog/FogEffect.cs b/BopVisualEffects/Effects/Fog/FogEffect.cs index 5e1a8c0..cabfa4d 100644 --- a/BopVisualEffects/Effects/Fog/FogEffect.cs +++ b/BopVisualEffects/Effects/Fog/FogEffect.cs @@ -36,7 +36,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) ["g"] = 0.8f, ["b"] = 0.9f, ["alpha"] = 0.6f, - ["height"] = 0.5f + ["height"] = 0.5f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -51,6 +52,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var b = entity.GetFloat("b"); var alpha = entity.GetFloat("alpha"); var height = entity.GetFloat("height"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -61,10 +63,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, r, g, b, alpha, height)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, r, g, b, alpha, height, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 5f), + new Keyframe(0.2f, 1f, 5f, 0f), + new Keyframe(0.8f, 1f, 0f, -5f), + new Keyframe(1f, 0f, -5f, 0f) + ); + private sealed class FogRunner : MonoBehaviour { private bool _initialized; @@ -78,11 +87,12 @@ private sealed class FogRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private FogOverlay? _overlay; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float r, float g, float b, float alpha, float height) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float r, float g, float b, float alpha, float height, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -93,6 +103,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _b = Mathf.Clamp01(b); _maxAlpha = Mathf.Clamp01(alpha); _height = Mathf.Clamp01(height); + _easingCurve = easingCurve; InitializeOverlay(); } @@ -129,13 +140,7 @@ private void LateUpdate() // Fade in over first 20%, hold, fade out over last 20%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.2f) - envelope = Mathf.InverseLerp(0f, 0.2f, progress); - else if (progress > 0.8f) - envelope = 1f - Mathf.InverseLerp(0.8f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_overlay != null) _overlay.SetParams(_r, _g, _b, _maxAlpha * envelope, _height); diff --git a/BopVisualEffects/Effects/Hsl/HslEffect.cs b/BopVisualEffects/Effects/Hsl/HslEffect.cs index d10126e..f45457c 100644 --- a/BopVisualEffects/Effects/Hsl/HslEffect.cs +++ b/BopVisualEffects/Effects/Hsl/HslEffect.cs @@ -37,7 +37,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) ["hue_shift"] = 0.0f, ["saturation"] = 1.0f, ["lightness"] = 0.0f, - ["intensity"] = 1.0f + ["intensity"] = 1.0f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -51,6 +52,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var saturation = entity.GetFloat("saturation"); var lightness = entity.GetFloat("lightness"); var intensity = entity.GetFloat("intensity"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -61,10 +63,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, hueShift, saturation, lightness, intensity)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, hueShift, saturation, lightness, intensity, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 5f), + new Keyframe(0.2f, 1f, 5f, 0f), + new Keyframe(0.8f, 1f, 0f, -5f), + new Keyframe(1f, 0f, -5f, 0f) + ); + private sealed class HslRunner : MonoBehaviour { private bool _initialized; @@ -78,12 +87,13 @@ private sealed class HslRunner : MonoBehaviour private JukeboxScript? _jukebox; private Camera? _camera; private HslRequest? _request; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, - float hueShift, float saturation, float lightness, float intensity) + float hueShift, float saturation, float lightness, float intensity, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -93,6 +103,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _saturation = Mathf.Max(0f, saturation); _lightness = Mathf.Clamp(lightness, -0.5f, 0.5f); _maxIntensity = Mathf.Clamp01(intensity); + _easingCurve = easingCurve; InitializeRequest(); } @@ -129,13 +140,7 @@ private void LateUpdate() // Fade in over first 20%, hold, fade out over last 20%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.2f) - envelope = Mathf.InverseLerp(0f, 0.2f, progress); - else if (progress > 0.8f) - envelope = 1f - Mathf.InverseLerp(0.8f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_request != null) { diff --git a/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs b/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs index 0cf0057..6cca885 100644 --- a/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs +++ b/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs @@ -32,7 +32,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["size"] = 0.1f + ["size"] = 0.1f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -43,6 +44,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var size = entity.GetFloat("size"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,10 +55,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, size)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, size, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 20f / 3f), + new Keyframe(0.15f, 1f, 20f / 3f, 0f), + new Keyframe(0.85f, 1f, 0f, -20f / 3f), + new Keyframe(1f, 0f, -20f / 3f, 0f) + ); + private sealed class LetterboxRunner : MonoBehaviour { private bool _initialized; @@ -66,17 +75,19 @@ private sealed class LetterboxRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private LetterboxOverlay? _overlay; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float size) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float size, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _size = Mathf.Clamp(size, 0f, 0.49f); + _easingCurve = easingCurve; InitializeOverlay(); } @@ -113,13 +124,7 @@ private void LateUpdate() // Slide bars in over first 15%, hold, slide out over last 15%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.15f) - envelope = Mathf.InverseLerp(0f, 0.15f, progress); - else if (progress > 0.85f) - envelope = 1f - Mathf.InverseLerp(0.85f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_overlay != null) _overlay.SetParams(_size * envelope); diff --git a/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs b/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs index d1e5f08..094edba 100644 --- a/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs +++ b/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs @@ -33,7 +33,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["pixel_size"] = 4.0f + ["pixel_size"] = 4.0f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -44,6 +45,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var pixelSize = entity.GetFloat("pixel_size"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -54,10 +56,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, pixelSize)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, pixelSize, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 20f / 3f), + new Keyframe(0.15f, 1f, 20f / 3f, 0f), + new Keyframe(0.85f, 1f, 0f, -20f / 3f), + new Keyframe(1f, 0f, -20f / 3f, 0f) + ); + private sealed class PixelGridRunner : MonoBehaviour { private bool _initialized; @@ -67,17 +76,19 @@ private sealed class PixelGridRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private PixelGridOverlay? _overlay; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float pixelSize) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float pixelSize, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _pixelSize = Mathf.Clamp(Mathf.RoundToInt(pixelSize), 2, 64); + _easingCurve = easingCurve; InitializeOverlay(); } @@ -115,13 +126,7 @@ private void LateUpdate() // Ramp block size up over first 15%, hold, ramp down over last 15%. // block_size=1 means no pixelation; ramping from 1 → _pixelSize gives a "zooming into pixels" look. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.15f) - envelope = Mathf.InverseLerp(0f, 0.15f, progress); - else if (progress > 0.85f) - envelope = 1f - Mathf.InverseLerp(0.85f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); var currentBlockSize = Mathf.Max(1, Mathf.RoundToInt(Mathf.Lerp(1f, _pixelSize, envelope))); if (_overlay != null) diff --git a/BopVisualEffects/Effects/Scanlines/ScanlinesEffect.cs b/BopVisualEffects/Effects/Scanlines/ScanlinesEffect.cs index af16a71..b93437f 100644 --- a/BopVisualEffects/Effects/Scanlines/ScanlinesEffect.cs +++ b/BopVisualEffects/Effects/Scanlines/ScanlinesEffect.cs @@ -34,7 +34,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) { ["alpha"] = 0.35f, ["count"] = 60.0f, - ["scroll_speed"] = 0.0f + ["scroll_speed"] = 0.0f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -47,6 +48,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var alpha = entity.GetFloat("alpha"); var count = entity.GetFloat("count"); var scrollSpeed = entity.GetFloat("scroll_speed"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -57,10 +59,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, scrollSpeed)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, scrollSpeed, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 20f / 3f), + new Keyframe(0.15f, 1f, 20f / 3f, 0f), + new Keyframe(0.85f, 1f, 0f, -20f / 3f), + new Keyframe(1f, 0f, -20f / 3f, 0f) + ); + private sealed class ScanlinesRunner : MonoBehaviour { private bool _initialized; @@ -72,11 +81,12 @@ private sealed class ScanlinesRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private ScanlinesOverlay? _overlay; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float scrollSpeed) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float scrollSpeed, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -85,6 +95,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _alpha = Mathf.Clamp01(alpha); _count = Mathf.Clamp(Mathf.RoundToInt(count), 4, 2000); _scrollSpeed = scrollSpeed; + _easingCurve = easingCurve; InitializeOverlay(); } @@ -121,13 +132,7 @@ private void LateUpdate() // Fade in over first 15%, hold, fade out over last 15%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.15f) - envelope = Mathf.InverseLerp(0f, 0.15f, progress); - else if (progress > 0.85f) - envelope = 1f - Mathf.InverseLerp(0.85f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_overlay != null) _overlay.SetParams(_alpha * envelope, _count, _scrollSpeed); diff --git a/BopVisualEffects/Effects/ScreenNoise/ScreenNoiseEffect.cs b/BopVisualEffects/Effects/ScreenNoise/ScreenNoiseEffect.cs index 4b0c240..b93486d 100644 --- a/BopVisualEffects/Effects/ScreenNoise/ScreenNoiseEffect.cs +++ b/BopVisualEffects/Effects/ScreenNoise/ScreenNoiseEffect.cs @@ -34,7 +34,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) { ["alpha"] = 0.5f, ["count"] = 400.0f, - ["size"] = 0.01f + ["size"] = 0.01f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -47,6 +48,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var alpha = entity.GetFloat("alpha"); var count = entity.GetFloat("count"); var size = entity.GetFloat("size"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -57,10 +59,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, size)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, size, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 20f / 3f), + new Keyframe(0.15f, 1f, 20f / 3f, 0f), + new Keyframe(0.85f, 1f, 0f, -20f / 3f), + new Keyframe(1f, 0f, -20f / 3f, 0f) + ); + private sealed class ScreenNoiseRunner : MonoBehaviour { private bool _initialized; @@ -72,11 +81,12 @@ private sealed class ScreenNoiseRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private ScreenNoiseOverlay? _overlay; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float size) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float size, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -85,6 +95,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _alpha = Mathf.Clamp01(alpha); _count = Mathf.Clamp(Mathf.RoundToInt(count), 10, 2000); _size = Mathf.Clamp(size, 0.005f, 0.1f); + _easingCurve = easingCurve; InitializeOverlay(); } @@ -121,13 +132,7 @@ private void LateUpdate() // Fade in over first 15%, hold, fade out over last 15%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.15f) - envelope = Mathf.InverseLerp(0f, 0.15f, progress); - else if (progress > 0.85f) - envelope = 1f - Mathf.InverseLerp(0.85f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_overlay != null) _overlay.SetParams(_alpha * envelope, _count, _size); diff --git a/BopVisualEffects/Effects/Sepia/SepiaEffect.cs b/BopVisualEffects/Effects/Sepia/SepiaEffect.cs index f2a8592..bbe3850 100644 --- a/BopVisualEffects/Effects/Sepia/SepiaEffect.cs +++ b/BopVisualEffects/Effects/Sepia/SepiaEffect.cs @@ -34,7 +34,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["intensity"] = 0.8f + ["intensity"] = 0.8f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -45,6 +46,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var intensity = entity.GetFloat("intensity"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -55,10 +57,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 5f), + new Keyframe(0.2f, 1f, 5f, 0f), + new Keyframe(0.8f, 1f, 0f, -5f), + new Keyframe(1f, 0f, -5f, 0f) + ); + private sealed class SepiaRunner : MonoBehaviour { private bool _initialized; @@ -69,17 +78,19 @@ private sealed class SepiaRunner : MonoBehaviour private JukeboxScript? _jukebox; private Camera? _camera; private SepiaRequest? _request; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _maxIntensity = Mathf.Clamp01(intensity); + _easingCurve = easingCurve; InitializeRequest(); } @@ -116,13 +127,7 @@ private void LateUpdate() // Fade in over first 20%, hold, fade out over last 20%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.2f) - envelope = Mathf.InverseLerp(0f, 0.2f, progress); - else if (progress > 0.8f) - envelope = 1f - Mathf.InverseLerp(0.8f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_request != null) _request.Intensity = _maxIntensity * envelope; diff --git a/BopVisualEffects/Effects/Vignette/VignetteEffect.cs b/BopVisualEffects/Effects/Vignette/VignetteEffect.cs index d872743..b0c0c9f 100644 --- a/BopVisualEffects/Effects/Vignette/VignetteEffect.cs +++ b/BopVisualEffects/Effects/Vignette/VignetteEffect.cs @@ -33,7 +33,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) properties = new Dictionary { ["alpha"] = 0.7f, - ["size"] = 0.1f + ["size"] = 0.1f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -45,6 +46,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var durationBeats = Mathf.Max(0.01f, entity.length); var alpha = entity.GetFloat("alpha"); var size = entity.GetFloat("size"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -55,10 +57,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, size)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, size, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 5f), + new Keyframe(0.2f, 1f, 5f, 0f), + new Keyframe(0.8f, 1f, 0f, -5f), + new Keyframe(1f, 0f, -5f, 0f) + ); + private sealed class VignetteRunner : MonoBehaviour { private bool _initialized; @@ -69,11 +78,12 @@ private sealed class VignetteRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private VignetteOverlay? _overlay; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float size) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float size, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -81,6 +91,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _endBeat = endBeat; _alpha = Mathf.Clamp01(alpha); _size = Mathf.Clamp(size, 0f, 0.5f); + _easingCurve = easingCurve; InitializeOverlay(); } @@ -117,13 +128,7 @@ private void LateUpdate() // Fade in over first 20%, hold, fade out over last 20%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.2f) - envelope = Mathf.InverseLerp(0f, 0.2f, progress); - else if (progress > 0.8f) - envelope = 1f - Mathf.InverseLerp(0.8f, 1f, progress); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); if (_overlay != null) _overlay.SetParams(_alpha * envelope, _size); diff --git a/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs b/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs index 403b489..6ecc942 100644 --- a/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs +++ b/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs @@ -32,7 +32,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["intensity"] = 0.2f + ["intensity"] = 0.2f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -43,6 +44,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var intensity = entity.GetFloat("intensity"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,10 +55,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 0f), + new Keyframe(0.2f, 1f, 0f, 0f), + new Keyframe(0.8f, 1f, 0f, 0f), + new Keyframe(1f, 0f, 0f, 0f) + ); + private sealed class ZoomInRunner : MonoBehaviour { private bool _initialized; @@ -66,17 +75,19 @@ private sealed class ZoomInRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private Camera? _camera; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _intensity = Mathf.Clamp(intensity, 0f, 0.99f); + _easingCurve = easingCurve; InitializeTargetCamera(); } @@ -114,13 +125,7 @@ private void LateUpdate() // Ease in over first 20%, hold zoomed in from 20-80%, ease back out over last 20%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.2f) - envelope = Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0f, 0.2f, progress)); - else if (progress > 0.8f) - envelope = Mathf.SmoothStep(0f, 1f, 1f - Mathf.InverseLerp(0.8f, 1f, progress)); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); // Zoom in: zoomFactor < 1 means smaller camera size = more zoomed in. var zoomFactor = Mathf.Clamp(1f - _intensity * envelope, 0.01f, 1f); diff --git a/BopVisualEffects/Effects/ZoomOut/ZoomOutEffect.cs b/BopVisualEffects/Effects/ZoomOut/ZoomOutEffect.cs index ff25b25..46b00c2 100644 --- a/BopVisualEffects/Effects/ZoomOut/ZoomOutEffect.cs +++ b/BopVisualEffects/Effects/ZoomOut/ZoomOutEffect.cs @@ -32,7 +32,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["intensity"] = 0.2f + ["intensity"] = 0.2f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -43,6 +44,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var intensity = entity.GetFloat("intensity"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,10 +55,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 0f), + new Keyframe(0.2f, 1f, 0f, 0f), + new Keyframe(0.8f, 1f, 0f, 0f), + new Keyframe(1f, 0f, 0f, 0f) + ); + private sealed class ZoomOutRunner : MonoBehaviour { private bool _initialized; @@ -66,17 +75,19 @@ private sealed class ZoomOutRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private Camera? _camera; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _intensity = Mathf.Max(0f, intensity); + _easingCurve = easingCurve; InitializeTargetCamera(); } @@ -114,13 +125,7 @@ private void LateUpdate() // Ease out over first 20%, hold zoomed out from 20-80%, ease back in over last 20%. var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - float envelope; - if (progress < 0.2f) - envelope = Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0f, 0.2f, progress)); - else if (progress > 0.8f) - envelope = Mathf.SmoothStep(0f, 1f, 1f - Mathf.InverseLerp(0.8f, 1f, progress)); - else - envelope = 1f; + var envelope = _easingCurve.Evaluate(progress); // Zoom out: zoomFactor > 1 means larger camera size = more zoomed out. var zoomFactor = 1f + _intensity * envelope; diff --git a/BopVisualEffects/Effects/ZoomPulse/ZoomPulseEffect.cs b/BopVisualEffects/Effects/ZoomPulse/ZoomPulseEffect.cs index 125cd7a..903b696 100644 --- a/BopVisualEffects/Effects/ZoomPulse/ZoomPulseEffect.cs +++ b/BopVisualEffects/Effects/ZoomPulse/ZoomPulseEffect.cs @@ -32,7 +32,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["intensity"] = 0.15f + ["intensity"] = 0.15f, + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -43,6 +44,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var intensity = entity.GetFloat("intensity"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,10 +55,16 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, intensity, easingCurve)); } } + private static AnimationCurve DefaultEasingCurve() => new AnimationCurve( + new Keyframe(0f, 0f, 0f, 10f / 3f), + new Keyframe(0.3f, 1f, 10f / 3f, -10f / 7f), + new Keyframe(1f, 0f, -10f / 7f, 0f) + ); + private sealed class ZoomPulseRunner : MonoBehaviour { private bool _initialized; @@ -66,17 +74,19 @@ private sealed class ZoomPulseRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private Camera? _camera; + private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float intensity, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _intensity = Mathf.Max(0f, intensity); + _easingCurve = easingCurve; InitializeTargetCamera(); } @@ -114,9 +124,7 @@ private void LateUpdate() // Zoom in quickly then ease back out (attack/decay envelope shaped like a triangle peaking at 30%). var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - var envelope = progress < 0.3f - ? Mathf.InverseLerp(0f, 0.3f, progress) - : 1f - Mathf.InverseLerp(0.3f, 1f, progress); + var envelope = _easingCurve.Evaluate(progress); var zoomDelta = _intensity * envelope; diff --git a/BopVisualEffects/Stubs/CiGameTypeStubs.cs b/BopVisualEffects/Stubs/CiGameTypeStubs.cs index 9a7e70a..139aa85 100644 --- a/BopVisualEffects/Stubs/CiGameTypeStubs.cs +++ b/BopVisualEffects/Stubs/CiGameTypeStubs.cs @@ -45,6 +45,18 @@ public float GetFloat(string key) _ => 0f }; } + + public AnimationCurve GetAnimationCurve(string key, AnimationCurve fallback) + { + if (!properties.TryGetValue(key, out object? value) || value is null) + return fallback; + + return value switch + { + AnimationCurve curveValue => curveValue, + _ => fallback + }; + } } public sealed class MixtapeLoaderCustom : MonoBehaviour From d7acfff4a8a41b103e7a75bd8136eb01280bb3e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 24 Feb 2026 21:35:57 +0000 Subject: [PATCH 3/3] Add easing_curve property to all effects with configurable AnimationCurve Co-authored-by: Brollyy <12004018+Brollyy@users.noreply.github.com> --- docs/effects/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/effects/README.md b/docs/effects/README.md index e11d8fd..05bb44a 100644 --- a/docs/effects/README.md +++ b/docs/effects/README.md @@ -13,6 +13,7 @@ Adds a temporary camera shake effect for impact, hits, drops, or strong rhythm a **Properties** - `amplitude`: Strength of the shake. - `frequency`: Speed of the shake movement. +- `easing_curve`: Animation curve controlling the shake strength over the event duration (default: linear decay from full strength at the start to zero at the end). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview media** @@ -31,6 +32,7 @@ Briefly tilts the camera on its Z-axis with a smooth swing arc. Great for expres **Properties** - `angle`: Maximum tilt angle in degrees (positive = counter-clockwise). +- `easing_curve`: Animation curve controlling the tilt angle over the event duration (default: smooth bell curve peaking at the midpoint, approximating a sine wave). - `length` (event length in editor): How long the tilt lasts, in beats. This event is resizable in the timeline. ## Color Tint @@ -44,6 +46,7 @@ Applies a sustained full-screen color tint that fades in, holds, then fades out. **Properties** - `r`, `g`, `b`: Tint color (0–1 each; default `1, 0, 0` — red). - `alpha`: Maximum opacity (0–1; default `0.25`). +- `easing_curve`: Animation curve controlling the tint opacity over the event duration (default: linear fade in over the first 20%, hold, linear fade out over the last 20%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Fog @@ -58,6 +61,7 @@ Draws a ground fog gradient overlay — opaque at the bottom of the screen, fadi - `r`, `g`, `b`: Fog color (0–1 each; default `0.8, 0.8, 0.9` — pale blue-grey). - `alpha`: Maximum opacity at the bottom of the screen (0–1; default `0.6`). - `height`: Normalized screen height at which the fog fully fades to transparent (0–1; default `0.5`). +- `easing_curve`: Animation curve controlling the fog opacity over the event duration (default: linear fade in over the first 20%, hold, linear fade out over the last 20%). - `length` (event length in editor): How long the fog lasts, in beats. This event is resizable in the timeline. ## Horizontal Flip @@ -84,6 +88,7 @@ Adjusts hue, saturation and lightness of the whole screen for creative colour gr - `saturation`: Saturation multiplier (0 = fully greyscale, 1 = unchanged, >1 = boosted; default `1.0`). Boost above 1 requires shader bundle. - `lightness`: Additive lightness offset (-0.5–0.5; default `0`). Positive values brighten, negative values darken. - `intensity`: Overall blend strength (0–1; default `1.0`). +- `easing_curve`: Animation curve controlling the effect intensity over the event duration (default: linear fade in over the first 20%, hold, linear fade out over the last 20%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Letterbox @@ -96,6 +101,7 @@ Adds cinematic black bars at the top and bottom of the screen for a dramatic wid **Properties** - `size`: Height of each bar as a normalized screen fraction (0–0.49; default `0.1`). +- `easing_curve`: Animation curve controlling the bar height over the event duration (default: linear slide in over the first 15%, hold, linear slide out over the last 15%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Pixel Grid @@ -108,6 +114,7 @@ Pixelates the screen by downsampling the camera's rendered output to a low-resol **Properties** - `pixel_size`: Size of each pixel block in screen pixels (2–64; default `4`). Larger values produce a more pronounced 8-bit look. +- `easing_curve`: Animation curve controlling the pixel block size over the event duration (default: linear ramp up over the first 15%, hold, linear ramp down over the last 15%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Scanlines @@ -122,6 +129,7 @@ Draws horizontal CRT-style scan lines over the screen for a retro 8-bit aestheti - `alpha`: Darkness of each scan line (0–1; default `0.35`). - `count`: Number of scan lines (default `60`, clamped 4–2000). - `scroll_speed`: Speed at which lines scroll upward, in cells per second (default `0` = static; negative = scroll downward). +- `easing_curve`: Animation curve controlling the scan line opacity over the event duration (default: linear fade in over the first 15%, hold, linear fade out over the last 15%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Screen Noise @@ -136,6 +144,7 @@ Draws animated TV static noise specks over the screen for a glitchy atmosphere. - `alpha`: Maximum opacity of the noise specks (0–1; default `0.5`). - `count`: Number of noise specks drawn per frame (10–2000; default `400`). - `size`: Physical size of each speck in normalized screen coordinates (0.005–0.1; default `0.01`). +- `easing_curve`: Animation curve controlling the noise opacity over the event duration (default: linear fade in over the first 15%, hold, linear fade out over the last 15%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Sepia @@ -148,6 +157,7 @@ Applies a warm vintage sepia-tone filter using a per-pixel shader (standard Adob **Properties** - `intensity`: Strength of the sepia toning (0–1; default `0.8`). +- `easing_curve`: Animation curve controlling the effect intensity over the event duration (default: linear fade in over the first 20%, hold, linear fade out over the last 20%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Vertical Flip @@ -172,6 +182,7 @@ Darkens the screen edges with a smooth radial/elliptical gradient using a 32-seg **Properties** - `alpha`: Darkness of the edge (0–1; default `0.7`). - `size`: How far the darkening extends inward as a fraction of screen half-height (0–0.5; default `0.1`). +- `easing_curve`: Animation curve controlling the vignette opacity over the event duration (default: linear fade in over the first 20%, hold, linear fade out over the last 20%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Zoom In @@ -184,6 +195,7 @@ Eases the camera smoothly into a zoomed-in view, holds at the target level, then **Properties** - `intensity`: How far to zoom in, as a fraction of the camera's base size (e.g. `0.2` = 20% closer). Clamped to `[0, 0.99]`. +- `easing_curve`: Animation curve controlling the zoom intensity over the event duration (default: smooth ease in over the first 20%, hold, smooth ease out over the last 20%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Zoom Out @@ -196,6 +208,7 @@ Eases the camera smoothly out to a wider view, holds, then eases back in. Great **Properties** - `intensity`: How far to zoom out, as a fraction of the camera's base size (e.g. `0.2` = 20% further out). +- `easing_curve`: Animation curve controlling the zoom intensity over the event duration (default: smooth ease out over the first 20%, hold, smooth ease back in over the last 20%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. ## Zoom Pulse @@ -208,5 +221,6 @@ Rapidly zooms the camera in and then eases it back out, creating a punchy "push- **Properties** - `intensity`: How much to zoom in, expressed as a fraction of the camera's base size (e.g. `0.15` = 15% zoom). +- `easing_curve`: Animation curve controlling the zoom intensity over the event duration (default: linear ramp up to peak at 30%, then linear ramp back down). - `length` (event length in editor): How long the pulse lasts, in beats. This event is resizable in the timeline.