diff --git a/BopVisualEffects/Core/EffectEnvelope.cs b/BopVisualEffects/Core/EffectEnvelope.cs new file mode 100644 index 0000000..52d7928 --- /dev/null +++ b/BopVisualEffects/Core/EffectEnvelope.cs @@ -0,0 +1,30 @@ +using UnityEngine; + +namespace BopVisualEffects.Core; + +/// +/// Shared envelope math for effects whose strength fades at event boundaries. +/// +internal static class EffectEnvelope +{ + /// + /// Evaluates a fade envelope while allowing either boundary fade to be disabled. + /// + /// Normalized event progress in the range 0..1. + /// Whether to fade in from zero at the start. + /// Whether to fade out to zero at the end. + /// End of the fade-in segment. + /// Start of the fade-out segment. + public static float Evaluate(float progress, bool easeIn, bool easeOut, float fadeInEnd = 0.2f, float fadeOutStart = 0.8f) + { + progress = Mathf.Clamp01(progress); + + if (easeIn && progress < fadeInEnd) + return Mathf.InverseLerp(0f, fadeInEnd, progress); + + if (easeOut && progress > fadeOutStart) + return 1f - Mathf.InverseLerp(fadeOutStart, 1f, progress); + + return 1f; + } +} diff --git a/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs b/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs index 6e34e13..89fd3c7 100644 --- a/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs +++ b/BopVisualEffects/Effects/ColorTint/ColorTintEffect.cs @@ -32,7 +32,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["color"] = new MixtapeEventTemplates.ColorField(new Color(1.0f, 0.0f, 0.0f, 0.25f)) + ["color"] = new MixtapeEventTemplates.ColorField(new Color(1.0f, 0.0f, 0.0f, 0.25f)), + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -43,6 +45,8 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var log = ClassLogger.GetForClass(); var durationBeats = Mathf.Max(0.01f, entity.length); var color = entity.GetColor("color"); + var easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,7 +57,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, color)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, color, easeIn, easeOut)); } } @@ -66,17 +70,21 @@ private sealed class ColorTintRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private ColorTintOverlay? _overlay; + private bool _easeIn; + private bool _easeOut; /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, Color color) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, Color color, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _color = new Color(Mathf.Clamp01(color.r), Mathf.Clamp01(color.g), Mathf.Clamp01(color.b), Mathf.Clamp01(color.a)); + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -113,13 +121,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut); if (_overlay != null) _overlay.SetColor(new Color(_color.r, _color.g, _color.b, _color.a * envelope)); diff --git a/BopVisualEffects/Effects/Fog/FogEffect.cs b/BopVisualEffects/Effects/Fog/FogEffect.cs index 05ff27a..548a9fd 100644 --- a/BopVisualEffects/Effects/Fog/FogEffect.cs +++ b/BopVisualEffects/Effects/Fog/FogEffect.cs @@ -33,7 +33,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) properties = new Dictionary { ["color"] = new MixtapeEventTemplates.ColorField(new Color(0.8f, 0.8f, 0.9f, 0.6f)), - ["height"] = 0.5f + ["height"] = 0.5f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -45,6 +47,8 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var durationBeats = Mathf.Max(0.01f, entity.length); var color = entity.GetColor("color"); var height = entity.GetFloat("height"); + var easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -55,7 +59,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, color, height)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, color, height, easeIn, easeOut)); } } @@ -69,11 +73,13 @@ private sealed class FogRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private FogOverlay? _overlay; + private bool _easeIn; + private bool _easeOut; /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, Color color, float height) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, Color color, float height, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; @@ -81,6 +87,8 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _endBeat = endBeat; _color = new Color(Mathf.Clamp01(color.r), Mathf.Clamp01(color.g), Mathf.Clamp01(color.b), Mathf.Clamp01(color.a)); _height = Mathf.Clamp01(height); + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -117,13 +125,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut); if (_overlay != null) _overlay.SetParams(new Color(_color.r, _color.g, _color.b, _color.a * envelope), _height); diff --git a/BopVisualEffects/Effects/Hsl/HslEffect.cs b/BopVisualEffects/Effects/Hsl/HslEffect.cs index d10126e..cfe0b9d 100644 --- a/BopVisualEffects/Effects/Hsl/HslEffect.cs +++ b/BopVisualEffects/Effects/Hsl/HslEffect.cs @@ -37,7 +37,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) ["hue_shift"] = 0.0f, ["saturation"] = 1.0f, ["lightness"] = 0.0f, - ["intensity"] = 1.0f + ["intensity"] = 1.0f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -51,6 +53,8 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var saturation = entity.GetFloat("saturation"); var lightness = entity.GetFloat("lightness"); var intensity = entity.GetFloat("intensity"); + var easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -61,7 +65,7 @@ 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, easeIn, easeOut)); } } @@ -78,12 +82,14 @@ private sealed class HslRunner : MonoBehaviour private JukeboxScript? _jukebox; private Camera? _camera; private HslRequest? _request; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; @@ -93,6 +99,8 @@ 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); + _easeIn = easeIn; + _easeOut = easeOut; InitializeRequest(); } @@ -129,13 +137,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut); if (_request != null) { @@ -178,4 +180,3 @@ private void RemoveRequest() } } } - diff --git a/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs b/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs index 0cf0057..06dcee6 100644 --- a/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs +++ b/BopVisualEffects/Effects/Letterbox/LetterboxEffect.cs @@ -32,7 +32,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["size"] = 0.1f + ["size"] = 0.1f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -43,6 +45,8 @@ 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 easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,7 +57,7 @@ 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, easeIn, easeOut)); } } @@ -66,17 +70,21 @@ private sealed class LetterboxRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private LetterboxOverlay? _overlay; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _size = Mathf.Clamp(size, 0f, 0.49f); + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -113,13 +121,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut, 0.15f, 0.85f); if (_overlay != null) _overlay.SetParams(_size * envelope); diff --git a/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs b/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs index d1e5f08..d1c52b4 100644 --- a/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs +++ b/BopVisualEffects/Effects/PixelGrid/PixelGridEffect.cs @@ -33,7 +33,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["pixel_size"] = 4.0f + ["pixel_size"] = 4.0f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -44,6 +46,8 @@ 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 easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -54,7 +58,7 @@ 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, easeIn, easeOut)); } } @@ -67,17 +71,21 @@ private sealed class PixelGridRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private PixelGridOverlay? _overlay; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _pixelSize = Mathf.Clamp(Mathf.RoundToInt(pixelSize), 2, 64); + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -115,13 +123,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut, 0.15f, 0.85f); 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..d2e4902 100644 --- a/BopVisualEffects/Effects/Scanlines/ScanlinesEffect.cs +++ b/BopVisualEffects/Effects/Scanlines/ScanlinesEffect.cs @@ -34,7 +34,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) { ["alpha"] = 0.35f, ["count"] = 60.0f, - ["scroll_speed"] = 0.0f + ["scroll_speed"] = 0.0f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -47,6 +49,8 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var alpha = entity.GetFloat("alpha"); var count = entity.GetFloat("count"); var scrollSpeed = entity.GetFloat("scroll_speed"); + var easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -57,7 +61,7 @@ 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, easeIn, easeOut)); } } @@ -72,11 +76,13 @@ private sealed class ScanlinesRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private ScanlinesOverlay? _overlay; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; @@ -85,6 +91,8 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _alpha = Mathf.Clamp01(alpha); _count = Mathf.Clamp(Mathf.RoundToInt(count), 4, 2000); _scrollSpeed = scrollSpeed; + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -121,13 +129,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut, 0.15f, 0.85f); 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..19e856b 100644 --- a/BopVisualEffects/Effects/ScreenNoise/ScreenNoiseEffect.cs +++ b/BopVisualEffects/Effects/ScreenNoise/ScreenNoiseEffect.cs @@ -34,7 +34,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) { ["alpha"] = 0.5f, ["count"] = 400.0f, - ["size"] = 0.01f + ["size"] = 0.01f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -47,6 +49,8 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var alpha = entity.GetFloat("alpha"); var count = entity.GetFloat("count"); var size = entity.GetFloat("size"); + var easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -57,7 +61,7 @@ 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, easeIn, easeOut)); } } @@ -72,11 +76,13 @@ private sealed class ScreenNoiseRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private ScreenNoiseOverlay? _overlay; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; @@ -85,6 +91,8 @@ 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); + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -121,13 +129,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut, 0.15f, 0.85f); 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..6a81cf9 100644 --- a/BopVisualEffects/Effects/Sepia/SepiaEffect.cs +++ b/BopVisualEffects/Effects/Sepia/SepiaEffect.cs @@ -34,7 +34,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["intensity"] = 0.8f + ["intensity"] = 0.8f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -45,6 +47,8 @@ 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 easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -55,7 +59,7 @@ 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, easeIn, easeOut)); } } @@ -69,17 +73,21 @@ private sealed class SepiaRunner : MonoBehaviour private JukeboxScript? _jukebox; private Camera? _camera; private SepiaRequest? _request; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _maxIntensity = Mathf.Clamp01(intensity); + _easeIn = easeIn; + _easeOut = easeOut; InitializeRequest(); } @@ -116,13 +124,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut); if (_request != null) _request.Intensity = _maxIntensity * envelope; @@ -155,4 +157,3 @@ private void RemoveRequest() } } } - diff --git a/BopVisualEffects/Effects/Vignette/VignetteEffect.cs b/BopVisualEffects/Effects/Vignette/VignetteEffect.cs index d872743..146ac9f 100644 --- a/BopVisualEffects/Effects/Vignette/VignetteEffect.cs +++ b/BopVisualEffects/Effects/Vignette/VignetteEffect.cs @@ -33,7 +33,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) properties = new Dictionary { ["alpha"] = 0.7f, - ["size"] = 0.1f + ["size"] = 0.1f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -45,6 +47,8 @@ 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 easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -55,7 +59,7 @@ 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, easeIn, easeOut)); } } @@ -69,11 +73,13 @@ private sealed class VignetteRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private VignetteOverlay? _overlay; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; @@ -81,6 +87,8 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _endBeat = endBeat; _alpha = Mathf.Clamp01(alpha); _size = Mathf.Clamp(size, 0f, 0.5f); + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -117,13 +125,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut); if (_overlay != null) _overlay.SetParams(_alpha * envelope, _size); diff --git a/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs b/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs index 403b489..38abf10 100644 --- a/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs +++ b/BopVisualEffects/Effects/ZoomIn/ZoomInEffect.cs @@ -32,7 +32,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["intensity"] = 0.2f + ["intensity"] = 0.2f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -43,6 +45,8 @@ 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 easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,7 +57,7 @@ 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, easeIn, easeOut)); } } @@ -66,17 +70,21 @@ private sealed class ZoomInRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private Camera? _camera; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _intensity = Mathf.Clamp(intensity, 0f, 0.99f); + _easeIn = easeIn; + _easeOut = easeOut; InitializeTargetCamera(); } @@ -114,13 +122,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut); // 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..0014015 100644 --- a/BopVisualEffects/Effects/ZoomOut/ZoomOutEffect.cs +++ b/BopVisualEffects/Effects/ZoomOut/ZoomOutEffect.cs @@ -32,7 +32,9 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) resizable = true, properties = new Dictionary { - ["intensity"] = 0.2f + ["intensity"] = 0.2f, + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -43,6 +45,8 @@ 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 easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; @@ -53,7 +57,7 @@ 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, easeIn, easeOut)); } } @@ -66,17 +70,21 @@ private sealed class ZoomOutRunner : MonoBehaviour private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; private Camera? _camera; + private bool _easeIn; + private bool _easeOut; /// /// 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, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _intensity = Mathf.Max(0f, intensity); + _easeIn = easeIn; + _easeOut = easeOut; InitializeTargetCamera(); } @@ -114,13 +122,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 = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut); // Zoom out: zoomFactor > 1 means larger camera size = more zoomed out. var zoomFactor = 1f + _intensity * envelope; diff --git a/docs/effects/README.md b/docs/effects/README.md index f9cb92a..cb96714 100644 --- a/docs/effects/README.md +++ b/docs/effects/README.md @@ -45,6 +45,8 @@ Applies a sustained full-screen color tint that fades in, holds, then fades out. **Properties** - `color`: Tint color (default `#ff000040` — red with slight opacity). +- `ease_in`: Fade in from zero at the event start (default `true`). +- `ease_out`: Fade out to zero at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -62,6 +64,8 @@ Draws a ground fog gradient overlay - opaque at the bottom of the screen, fading **Properties** - `color`: Fog color (default `#CCCCE599` - pale blue-gray). - `height`: Normalized screen height at which the fog fully fades to transparent (0–1; default `0.5`). +- `ease_in`: Fade in from zero at the event start (default `true`). +- `ease_out`: Fade out to zero at the event end (default `true`). - `length` (event length in editor): How long the fog lasts, in beats. This event is resizable in the timeline. **Preview** @@ -96,6 +100,8 @@ Adjusts hue, saturation and lightness of the whole screen for colour grading usi - `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`). +- `ease_in`: Fade in from zero at the event start (default `true`). +- `ease_out`: Fade out to zero at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -112,6 +118,8 @@ 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`). +- `ease_in`: Slide bars in at the event start (default `true`). +- `ease_out`: Slide bars out at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -128,6 +136,8 @@ 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. +- `ease_in`: Ramp pixelation in at the event start (default `true`). +- `ease_out`: Ramp pixelation out at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -146,6 +156,8 @@ Draws horizontal CRT-style scan lines over the screen for a retro aesthetic. Can - `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). +- `ease_in`: Fade scanlines in at the event start (default `true`). +- `ease_out`: Fade scanlines out at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -164,6 +176,8 @@ 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`). +- `ease_in`: Fade noise in at the event start (default `true`). +- `ease_out`: Fade noise out at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -180,6 +194,8 @@ 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`). +- `ease_in`: Fade sepia in at the event start (default `true`). +- `ease_out`: Fade sepia out at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -212,6 +228,8 @@ 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`). +- `ease_in`: Fade vignette in at the event start (default `true`). +- `ease_out`: Fade vignette out at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -228,6 +246,8 @@ 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]`. +- `ease_in`: Ease into the zoom at the event start (default `true`). +- `ease_out`: Ease out of the zoom at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** @@ -244,6 +264,8 @@ Eases the camera smoothly out to a wider view, holds, then eases back in. Compos **Properties** - `intensity`: How far to zoom out, as a fraction of the camera's base size (e.g. `0.2` = 20% further out). +- `ease_in`: Ease into the zoom at the event start (default `true`). +- `ease_out`: Ease out of the zoom at the event end (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview**