From 82aa823ad256db6f61e6b9b69a9a49622d5c697c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 05:50:23 +0000 Subject: [PATCH 01/10] Initial plan From 8d6009838912e099e4b1da620d31830eebd56d30 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 06:03:29 +0000 Subject: [PATCH 02/10] feat: add Speed Lines effect Co-authored-by: Brollyy <12004018+Brollyy@users.noreply.github.com> --- .../Core/EffectDefinitionRegistry.cs | 2 + .../Effects/SpeedLines/SpeedLinesEffect.cs | 310 ++++++++++++++++++ docs/effects/README.md | 18 + 3 files changed, 330 insertions(+) create mode 100644 BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs diff --git a/BopVisualEffects/Core/EffectDefinitionRegistry.cs b/BopVisualEffects/Core/EffectDefinitionRegistry.cs index 92c05f4..34d4552 100644 --- a/BopVisualEffects/Core/EffectDefinitionRegistry.cs +++ b/BopVisualEffects/Core/EffectDefinitionRegistry.cs @@ -13,6 +13,7 @@ using BopVisualEffects.Effects.Scanlines; using BopVisualEffects.Effects.ScreenNoise; using BopVisualEffects.Effects.Sepia; +using BopVisualEffects.Effects.SpeedLines; using BopVisualEffects.Effects.VerticalFlip; using BopVisualEffects.Effects.Vignette; using BopVisualEffects.Effects.ZoomIn; @@ -65,6 +66,7 @@ public static void Initialize(string pluginGuid, ClassLogger log, ConfigFile con registry.Register(new ScanlinesEffect()); registry.Register(new ScreenNoiseEffect()); registry.Register(new SepiaEffect()); + registry.Register(new SpeedLinesEffect()); registry.Register(new VerticalFlipEffect()); registry.Register(new VignetteEffect()); registry.Register(new ZoomInEffect()); diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs new file mode 100644 index 0000000..5648d17 --- /dev/null +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using BopVisualEffects.Core; +using UnityEngine; +using UnityEngine.Rendering; + +namespace BopVisualEffects.Effects.SpeedLines; + +/// +/// Effect definition for animated speed-line triangles radiating inward from the screen edges. +/// +public sealed class SpeedLinesEffect : IVisualEffectDefinition +{ + /// + public string Id => "speed lines"; + + /// + public string DisplayName => "Speed Lines"; + + /// + public string ConfigKey => "SpeedLines"; + + /// + public string Description => "Draws animated black triangles radiating inward from the screen edges for a high-speed rush feeling."; + + /// + public MixtapeEventTemplate CreateTemplate(string pluginGuid) + { + return new MixtapeEventTemplate + { + dataModel = $"{pluginGuid}/{Id}", + length = 2.0f, + resizable = true, + properties = new Dictionary + { + ["alpha"] = 0.8f, + ["count"] = 20.0f, + ["speed"] = 3.5f + } + }; + } + + /// + public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) + { + var log = ClassLogger.GetForClass(); + var durationBeats = Mathf.Max(0.01f, entity.length); + var alpha = entity.GetFloat("alpha"); + var count = entity.GetFloat("count"); + var speed = entity.GetFloat("speed"); + var startBeat = entity.beat; + var endBeat = startBeat + durationBeats; + + loader.scheduler.Schedule(startBeat, (Action?)SpawnAction); + log.Debug($"Scheduled '{DisplayName}' from beat {startBeat:0.###} to {endBeat:0.###}."); + return true; + + void SpawnAction() + { + EffectRuntimeController.Instance.SpawnRunner(runner => + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, speed)); + } + } + + private sealed class SpeedLinesRunner : MonoBehaviour + { + private bool _initialized; + private float _alpha; + private int _count; + private float _speed; + private float _startBeat; + private float _endBeat; + private MixtapeLoaderCustom? _loader; + private JukeboxScript? _jukebox; + private SpeedLinesOverlay? _overlay; + + /// + /// Initializes this runner with effect parameters. + /// + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float speed) + { + _loader = loader; + _jukebox = jukebox; + _startBeat = startBeat; + _endBeat = endBeat; + _alpha = Mathf.Clamp01(alpha); + _count = Mathf.Clamp(Mathf.RoundToInt(count), 4, 64); + _speed = Mathf.Max(0.1f, speed); + InitializeOverlay(); + } + + /// + /// Stops this effect instance. + /// + public void Stop() + { + RemoveOverlay(); + Destroy(this); + } + + private void LateUpdate() + { + if (_jukebox is null) + { + Stop(); + return; + } + + if (!_initialized) + { + InitializeOverlay(); + if (!_initialized) + return; + } + + var currentBeat = _jukebox.CurrentBeat; + if (currentBeat >= _endBeat) + { + Stop(); + return; + } + + // 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; + + if (_overlay != null) + _overlay.SetParams(_alpha * envelope, _speed); + } + + private void OnDisable() + { + RemoveOverlay(); + } + + private void InitializeOverlay() + { + Camera? camera = EffectRuntimeController.ResolveEffectCamera(_loader); + if (camera is null) + return; + + _overlay = camera.gameObject.AddComponent(); + _overlay.Initialize(_count, _startBeat); + _overlay.SetParams(_alpha, _speed); + _initialized = true; + } + + private void RemoveOverlay() + { + if (_overlay != null) + Destroy(_overlay); + _overlay = null; + } + } + + /// + /// Draws animated black speed-line triangles in OnPostRender. + /// Each triangle points inward from the screen edge toward the center, + /// flickering at an independently randomised phase to create a high-speed rush effect. + /// Must be attached to a Camera's GameObject. + /// + private sealed class SpeedLinesOverlay : MonoBehaviour + { + // Distance from screen centre to the triangle tip, in screen-height fractions. + private const float InnerRadius = 0.12f; + + // Half-width of the triangle base at the screen boundary, in screen-height fractions. + private const float HalfWidth = 0.025f; + + private static Material? _material; + private float _alpha; + private float _speed; + private float[]? _angles; + private float[]? _phases; + + /// + /// Generates the per-triangle angular positions and flicker phase offsets + /// using a deterministic seed so the layout is stable for each effect instance. + /// + public void Initialize(int count, float seed) + { + var rng = new Random(Mathf.FloorToInt(seed * 100f) + count * 7919); + _angles = new float[count]; + _phases = new float[count]; + for (var i = 0; i < count; i++) + { + // Stratified random angles: distribute evenly around the screen with a small + // random jitter so the layout looks natural but not perfectly uniform. + var baseAngle = 2f * Mathf.PI * i / count; + var jitter = (float)(rng.NextDouble() - 0.5) * (2f * Mathf.PI / count) * 0.5f; + _angles[i] = baseAngle + jitter; + _phases[i] = (float)(rng.NextDouble() * 2.0 * Math.PI); + } + } + + /// + /// Updates the maximum opacity and flicker speed applied to all speed lines. + /// + public void SetParams(float alpha, float speed) + { + _alpha = alpha; + _speed = speed; + } + + private static Material? GetMaterial() + { + if (!_material) + { + var shader = Shader.Find("Hidden/Internal-Colored"); + if (shader is null) + return null; + + _material = new Material(shader) { hideFlags = HideFlags.HideAndDontSave }; + _material.SetInt("_SrcBlend", (int)BlendMode.SrcAlpha); + _material.SetInt("_DstBlend", (int)BlendMode.OneMinusSrcAlpha); + _material.SetInt("_Cull", (int)CullMode.Off); + _material.SetInt("_ZWrite", 0); + } + + return _material; + } + + // Returns the screen-boundary intersection from the centre (0.5, 0.5) in + // direction (dx, dy), expressed in GL ortho space (x: 0..1, y: 0..1). + private static void ScreenBoundary(float dx, float dy, out float bx, out float by) + { + var tx = (dx != 0f) ? 0.5f / Mathf.Abs(dx) : float.MaxValue; + var ty = (dy != 0f) ? 0.5f / Mathf.Abs(dy) : float.MaxValue; + var t = Mathf.Min(tx, ty); + bx = 0.5f + t * dx; + by = 0.5f + t * dy; + } + + // Unity calls this on the camera's GameObject after it finishes rendering the scene. + private void OnPostRender() + { + if (_alpha <= 0f || _angles is null || _phases is null) + return; + + var mat = GetMaterial(); + if (mat is null) + return; + + var cam = GetComponent(); + // invAspect is used to convert the visual direction to GL ortho space. + var aspect = (cam != null) ? cam.aspect : (16f / 9f); + var invAspect = (aspect > 0f) ? 1f / aspect : 1f; + + var time = Time.time; + + mat.SetPass(0); + GL.PushMatrix(); + GL.LoadOrtho(); + GL.Begin(GL.TRIANGLES); + + for (var i = 0; i < _angles.Length; i++) + { + // Squaring the sine value produces sharper bright pulses separated by longer dark + // periods, reinforcing the high-speed feel. Each triangle uses a unique phase + // so they flash at different times rather than all at once. + var squaredSin = Mathf.Sin(time * _speed + _phases[i]); + squaredSin *= squaredSin; + var flickerAlpha = squaredSin * _alpha; + if (flickerAlpha <= 0.005f) + continue; + + var angle = _angles[i]; + var cosA = Mathf.Cos(angle); + var sinA = Mathf.Sin(angle); + + // Direction and perpendicular in GL ortho space (x-axis scaled by invAspect + // so angles and widths appear visually correct on screen). + var dirX = cosA * invAspect; + var dirY = sinA; + + // Triangle tip: InnerRadius away from screen centre along the visual direction. + var tipX = 0.5f + cosA * InnerRadius * invAspect; + var tipY = 0.5f + sinA * InnerRadius; + + // Triangle base: centred on the screen boundary at this angle. + ScreenBoundary(dirX, dirY, out var bx, out var by); + + // Perpendicular in GL ortho space (visual unit vector → GL representation). + var perpX = -sinA * invAspect; + var perpY = cosA; + + // Offset the base centre by ±HalfWidth along the perpendicular. + var base1X = bx + perpX * HalfWidth; + var base1Y = by + perpY * HalfWidth; + var base2X = bx - perpX * HalfWidth; + var base2Y = by - perpY * HalfWidth; + + var color = new Color(0f, 0f, 0f, flickerAlpha); + GL.Color(color); + GL.Vertex3(tipX, tipY, 0f); + GL.Vertex3(base1X, base1Y, 0f); + GL.Vertex3(base2X, base2Y, 0f); + } + + GL.End(); + GL.PopMatrix(); + } + } +} diff --git a/docs/effects/README.md b/docs/effects/README.md index f9cb92a..584b74c 100644 --- a/docs/effects/README.md +++ b/docs/effects/README.md @@ -186,6 +186,24 @@ Applies a warm vintage sepia-tone filter using a per-pixel shader (standard Adob https://github.com/user-attachments/assets/ff1025b3-1f4b-44c8-982f-6691fe861277 +## Speed Lines + +**DisplayName:** `Speed Lines` + +**Config Key:** `SpeedLines.Enabled` + +Draws animated black triangles radiating inward from the screen edges to convey a feeling of high speed. Each triangle independently flickers at its own phase, creating a dynamic rush effect. Fades in and out smoothly. + +**Properties** +- `alpha`: Maximum opacity of the triangles (0–1; default `0.8`). +- `count`: Number of speed-line triangles (4–64; default `20`). +- `speed`: Flicker rate of each triangle in cycles per second (default `3.5`). Higher values produce more frantic flickering; lower values give slower, more rhythmic pulses. +- `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. + +**Preview** + + + ## Vertical Flip **DisplayName:** `Vertical Flip` From cf7ccc5c01cd72803eb882de8038851e31ce85ae Mon Sep 17 00:00:00 2001 From: Brollyy Date: Wed, 18 Mar 2026 07:36:54 +0100 Subject: [PATCH 03/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs index 5648d17..6aa27f8 100644 --- a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs @@ -264,7 +264,7 @@ private void OnPostRender() // Squaring the sine value produces sharper bright pulses separated by longer dark // periods, reinforcing the high-speed feel. Each triangle uses a unique phase // so they flash at different times rather than all at once. - var squaredSin = Mathf.Sin(time * _speed + _phases[i]); + var squaredSin = Mathf.Sin(time * _speed * (2f * Mathf.PI) + _phases[i]); squaredSin *= squaredSin; var flickerAlpha = squaredSin * _alpha; if (flickerAlpha <= 0.005f) From f406f3ec167a751ad50f62b274b87d77c850ac75 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 06:41:56 +0000 Subject: [PATCH 04/10] fix: resolve build errors in SpeedLinesEffect Co-authored-by: Brollyy <12004018+Brollyy@users.noreply.github.com> --- BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs index 6aa27f8..e8fa2b3 100644 --- a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using BopVisualEffects.Core; using UnityEngine; @@ -51,7 +50,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var startBeat = entity.beat; var endBeat = startBeat + durationBeats; - loader.scheduler.Schedule(startBeat, (Action?)SpawnAction); + loader.scheduler.Schedule(startBeat, (System.Action?)SpawnAction); log.Debug($"Scheduled '{DisplayName}' from beat {startBeat:0.###} to {endBeat:0.###}."); return true; @@ -185,7 +184,7 @@ private sealed class SpeedLinesOverlay : MonoBehaviour /// public void Initialize(int count, float seed) { - var rng = new Random(Mathf.FloorToInt(seed * 100f) + count * 7919); + var rng = new System.Random(Mathf.FloorToInt(seed * 100f) + count * 7919); _angles = new float[count]; _phases = new float[count]; for (var i = 0; i < count; i++) @@ -195,7 +194,7 @@ public void Initialize(int count, float seed) var baseAngle = 2f * Mathf.PI * i / count; var jitter = (float)(rng.NextDouble() - 0.5) * (2f * Mathf.PI / count) * 0.5f; _angles[i] = baseAngle + jitter; - _phases[i] = (float)(rng.NextDouble() * 2.0 * Math.PI); + _phases[i] = (float)(rng.NextDouble() * 2.0 * System.Math.PI); } } From 60b5560812973720717047f2377cc058c4fa277b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 08:57:26 +0000 Subject: [PATCH 05/10] style: use American English spelling in SpeedLinesEffect comments Co-authored-by: Brollyy <12004018+Brollyy@users.noreply.github.com> --- .../Effects/SpeedLines/SpeedLinesEffect.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs index e8fa2b3..81bdd1d 100644 --- a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs @@ -161,12 +161,12 @@ private void RemoveOverlay() /// /// Draws animated black speed-line triangles in OnPostRender. /// Each triangle points inward from the screen edge toward the center, - /// flickering at an independently randomised phase to create a high-speed rush effect. + /// flickering at an independently randomized phase to create a high-speed rush effect. /// Must be attached to a Camera's GameObject. /// private sealed class SpeedLinesOverlay : MonoBehaviour { - // Distance from screen centre to the triangle tip, in screen-height fractions. + // Distance from screen center to the triangle tip, in screen-height fractions. private const float InnerRadius = 0.12f; // Half-width of the triangle base at the screen boundary, in screen-height fractions. @@ -225,7 +225,7 @@ public void SetParams(float alpha, float speed) return _material; } - // Returns the screen-boundary intersection from the centre (0.5, 0.5) in + // Returns the screen-boundary intersection from the center (0.5, 0.5) in // direction (dx, dy), expressed in GL ortho space (x: 0..1, y: 0..1). private static void ScreenBoundary(float dx, float dy, out float bx, out float by) { @@ -278,18 +278,18 @@ private void OnPostRender() var dirX = cosA * invAspect; var dirY = sinA; - // Triangle tip: InnerRadius away from screen centre along the visual direction. + // Triangle tip: InnerRadius away from screen center along the visual direction. var tipX = 0.5f + cosA * InnerRadius * invAspect; var tipY = 0.5f + sinA * InnerRadius; - // Triangle base: centred on the screen boundary at this angle. + // Triangle base: centered on the screen boundary at this angle. ScreenBoundary(dirX, dirY, out var bx, out var by); // Perpendicular in GL ortho space (visual unit vector → GL representation). var perpX = -sinA * invAspect; var perpY = cosA; - // Offset the base centre by ±HalfWidth along the perpendicular. + // Offset the base center by ±HalfWidth along the perpendicular. var base1X = bx + perpX * HalfWidth; var base1Y = by + perpY * HalfWidth; var base2X = bx - perpX * HalfWidth; From 11544568bf44edd9d025a079eebd44172e6fb2b8 Mon Sep 17 00:00:00 2001 From: Brollyy Date: Mon, 7 Sep 2026 23:45:52 +0200 Subject: [PATCH 06/10] refactor: render speed lines with shader --- .../Effects/SpeedLines/SpeedLinesEffect.cs | 148 ++++++++++-------- .../Effects/SpeedLines/SpeedLinesService.cs | 116 ++++++++++++++ .../MixtapeEditorScriptPlaybackPatch.cs | 6 +- ...bopvisualeffects_shaders_linux.assetbundle | Bin 10664 -> 14798 bytes .../bopvisualeffects_shaders_osx.assetbundle | Bin 8232 -> 9866 bytes .../bopvisualeffects_shaders_win.assetbundle | Bin 7651 -> 9130 bytes .../Assets/Editor/BuildAssetBundles.cs | 1 + .../BopVisualEffects_SpeedLines.shader | 88 +++++++++++ .../BopVisualEffects_SpeedLines.shader.meta | 9 ++ BopVisualEffectsShaders/README.md | 1 + docs/effects/README.md | 10 +- 11 files changed, 302 insertions(+), 77 deletions(-) create mode 100644 BopVisualEffects/Effects/SpeedLines/SpeedLinesService.cs create mode 100644 BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader create mode 100644 BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader.meta diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs index 81bdd1d..1c9535d 100644 --- a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs @@ -10,6 +10,8 @@ namespace BopVisualEffects.Effects.SpeedLines; /// public sealed class SpeedLinesEffect : IVisualEffectDefinition { + private const float DefaultReach = 0.25f; + /// public string Id => "speed lines"; @@ -20,7 +22,7 @@ public sealed class SpeedLinesEffect : IVisualEffectDefinition public string ConfigKey => "SpeedLines"; /// - public string Description => "Draws animated black triangles radiating inward from the screen edges for a high-speed rush feeling."; + public string Description => "Applies an animated, colored radial speed-line post-processing effect for a high-speed rush feeling."; /// public MixtapeEventTemplate CreateTemplate(string pluginGuid) @@ -33,8 +35,10 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) properties = new Dictionary { ["alpha"] = 0.8f, - ["count"] = 20.0f, - ["speed"] = 3.5f + ["count"] = 96.0f, + ["speed"] = 3.5f, + ["reach"] = DefaultReach, + ["color"] = new MixtapeEventTemplates.ColorField(Color.white) } }; } @@ -47,9 +51,10 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var alpha = entity.GetFloat("alpha"); var count = entity.GetFloat("count"); var speed = entity.GetFloat("speed"); + var reach = entity.GetFloat("reach"); + var color = entity.GetColor("color"); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; - loader.scheduler.Schedule(startBeat, (System.Action?)SpawnAction); log.Debug($"Scheduled '{DisplayName}' from beat {startBeat:0.###} to {endBeat:0.###}."); return true; @@ -57,7 +62,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, speed)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, speed, reach, color)); } } @@ -67,24 +72,29 @@ private sealed class SpeedLinesRunner : MonoBehaviour private float _alpha; private int _count; private float _speed; + private float _reach; + private Color _color; private float _startBeat; private float _endBeat; private MixtapeLoaderCustom? _loader; private JukeboxScript? _jukebox; - private SpeedLinesOverlay? _overlay; + private Camera? _camera; + private SpeedLinesRequest? _request; /// /// Initializes this runner with effect parameters. /// - public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float speed) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float speed, float reach, Color color) { _loader = loader; _jukebox = jukebox; _startBeat = startBeat; _endBeat = endBeat; _alpha = Mathf.Clamp01(alpha); - _count = Mathf.Clamp(Mathf.RoundToInt(count), 4, 64); + _count = Mathf.Clamp(Mathf.RoundToInt(count), 24, 192); _speed = Mathf.Max(0.1f, speed); + _reach = Mathf.Clamp01(reach); + _color = new Color(Mathf.Clamp01(color.r), Mathf.Clamp01(color.g), Mathf.Clamp01(color.b), Mathf.Clamp01(color.a)); InitializeOverlay(); } @@ -129,8 +139,8 @@ private void LateUpdate() else envelope = 1f; - if (_overlay != null) - _overlay.SetParams(_alpha * envelope, _speed); + if (_request != null) + _request.Alpha = _alpha * envelope; } private void OnDisable() @@ -144,58 +154,54 @@ private void InitializeOverlay() if (camera is null) return; - _overlay = camera.gameObject.AddComponent(); - _overlay.Initialize(_count, _startBeat); - _overlay.SetParams(_alpha, _speed); + _camera = camera; + _request = SpeedLinesService.AddRequest(camera); + _request.Alpha = _alpha; + _request.Count = _count; + _request.Speed = _speed; + _request.Reach = _reach; + _request.Color = _color; _initialized = true; } private void RemoveOverlay() { - if (_overlay != null) - Destroy(_overlay); - _overlay = null; + if (_camera != null && _request != null) + SpeedLinesService.RemoveRequest(_camera, _request); + _camera = null; + _request = null; } } /// - /// Draws animated black speed-line triangles in OnPostRender. - /// Each triangle points inward from the screen edge toward the center, - /// flickering at an independently randomized phase to create a high-speed rush effect. + /// Draws animated radial speed-line streaks in OnPostRender. + /// Each streak has an independent phase, speed, and length so the effect remains visible + /// while continuously moving toward the screen edge. /// Must be attached to a Camera's GameObject. /// private sealed class SpeedLinesOverlay : MonoBehaviour { - // Distance from screen center to the triangle tip, in screen-height fractions. - private const float InnerRadius = 0.12f; - - // Half-width of the triangle base at the screen boundary, in screen-height fractions. - private const float HalfWidth = 0.025f; + // Half-width of a streak at the outer end, in screen-height fractions. + private const float HalfWidth = 0.009f; private static Material? _material; private float _alpha; private float _speed; - private float[]? _angles; - private float[]? _phases; + private float _reach; + private Color _color; + private int _count; + private float _seed; /// /// Generates the per-triangle angular positions and flicker phase offsets /// using a deterministic seed so the layout is stable for each effect instance. /// - public void Initialize(int count, float seed) + public void Initialize(int count, float seed, float reach, Color color) { - var rng = new System.Random(Mathf.FloorToInt(seed * 100f) + count * 7919); - _angles = new float[count]; - _phases = new float[count]; - for (var i = 0; i < count; i++) - { - // Stratified random angles: distribute evenly around the screen with a small - // random jitter so the layout looks natural but not perfectly uniform. - var baseAngle = 2f * Mathf.PI * i / count; - var jitter = (float)(rng.NextDouble() - 0.5) * (2f * Mathf.PI / count) * 0.5f; - _angles[i] = baseAngle + jitter; - _phases[i] = (float)(rng.NextDouble() * 2.0 * System.Math.PI); - } + _count = count; + _seed = seed; + _reach = Mathf.Clamp01(reach); + _color = color; } /// @@ -236,10 +242,16 @@ private static void ScreenBoundary(float dx, float dy, out float bx, out float b by = 0.5f + t * dy; } + private static float Hash01(int x, int y) + { + var value = Mathf.Sin(x * 127.1f + y * 311.7f) * 43758.5453f; + return value - Mathf.Floor(value); + } + // Unity calls this on the camera's GameObject after it finishes rendering the scene. private void OnPostRender() { - if (_alpha <= 0f || _angles is null || _phases is null) + if (_alpha <= 0f || _count <= 0 || _reach <= 0f) return; var mat = GetMaterial(); @@ -258,18 +270,11 @@ private void OnPostRender() GL.LoadOrtho(); GL.Begin(GL.TRIANGLES); - for (var i = 0; i < _angles.Length; i++) + var innerLimit = 1f - _reach; + for (var i = 0; i < _count; i++) { - // Squaring the sine value produces sharper bright pulses separated by longer dark - // periods, reinforcing the high-speed feel. Each triangle uses a unique phase - // so they flash at different times rather than all at once. - var squaredSin = Mathf.Sin(time * _speed * (2f * Mathf.PI) + _phases[i]); - squaredSin *= squaredSin; - var flickerAlpha = squaredSin * _alpha; - if (flickerAlpha <= 0.005f) - continue; - - var angle = _angles[i]; + var angleNoise = Hash01(i, Mathf.FloorToInt(_seed * 100f)); + var angle = 2f * Mathf.PI * (i + 0.5f + (angleNoise - 0.5f) * 0.7f) / _count; var cosA = Mathf.Cos(angle); var sinA = Mathf.Sin(angle); @@ -278,28 +283,35 @@ private void OnPostRender() var dirX = cosA * invAspect; var dirY = sinA; - // Triangle tip: InnerRadius away from screen center along the visual direction. - var tipX = 0.5f + cosA * InnerRadius * invAspect; - var tipY = 0.5f + sinA * InnerRadius; - - // Triangle base: centered on the screen boundary at this angle. - ScreenBoundary(dirX, dirY, out var bx, out var by); + // Find the screen-edge endpoint for this aspect-corrected visual ray. + ScreenBoundary(dirX, dirY, out var edgeX, out var edgeY); // Perpendicular in GL ortho space (visual unit vector → GL representation). var perpX = -sinA * invAspect; var perpY = cosA; - // Offset the base center by ±HalfWidth along the perpendicular. - var base1X = bx + perpX * HalfWidth; - var base1Y = by + perpY * HalfWidth; - var base2X = bx - perpX * HalfWidth; - var base2Y = by - perpY * HalfWidth; - - var color = new Color(0f, 0f, 0f, flickerAlpha); - GL.Color(color); - GL.Vertex3(tipX, tipY, 0f); - GL.Vertex3(base1X, base1Y, 0f); - GL.Vertex3(base2X, base2Y, 0f); + var phase = Hash01(i + 17, Mathf.FloorToInt(_seed * 100f)); + var rate = Mathf.Lerp(0.75f, 1.35f, Hash01(i + 31, Mathf.FloorToInt(_seed * 47f))); + var progress = Mathf.Repeat(time * _speed * rate + phase, 1f); + var innerT = Mathf.Lerp(innerLimit, 1f, progress); + var outerT = Mathf.Min(1f, innerT + Mathf.Lerp(0.14f, 0.32f, Hash01(i + 53, Mathf.FloorToInt(_seed * 71f)))); + if (outerT - innerT <= 0.001f) + continue; + + var innerX = Mathf.Lerp(0.5f, edgeX, innerT); + var innerY = Mathf.Lerp(0.5f, edgeY, innerT); + var outerX = Mathf.Lerp(0.5f, edgeX, outerT); + var outerY = Mathf.Lerp(0.5f, edgeY, outerT); + var innerWidth = HalfWidth * 0.12f; + var outerWidth = HalfWidth * Mathf.Lerp(0.7f, 1.15f, outerT); + var pulse = 0.75f + 0.25f * Mathf.Sin(time * _speed * rate * (2f * Mathf.PI) + phase * 2f * Mathf.PI); + GL.Color(new Color(_color.r, _color.g, _color.b, _alpha * _color.a * pulse)); + GL.Vertex3(innerX + perpX * innerWidth, innerY + perpY * innerWidth, 0f); + GL.Vertex3(outerX + perpX * outerWidth, outerY + perpY * outerWidth, 0f); + GL.Vertex3(outerX - perpX * outerWidth, outerY - perpY * outerWidth, 0f); + GL.Vertex3(innerX + perpX * innerWidth, innerY + perpY * innerWidth, 0f); + GL.Vertex3(outerX - perpX * outerWidth, outerY - perpY * outerWidth, 0f); + GL.Vertex3(innerX - perpX * innerWidth, innerY - perpY * innerWidth, 0f); } GL.End(); diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesService.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesService.cs new file mode 100644 index 0000000..1379ed8 --- /dev/null +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesService.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using BopVisualEffects.Core; +using UnityEngine; + +namespace BopVisualEffects.Effects.SpeedLines; + +internal sealed class SpeedLinesRequest +{ + public float Alpha; + public float Count; + public float Speed; + public float Reach; + public Color Color = Color.white; +} + +internal static class SpeedLinesService +{ + private sealed class CameraState + { + public readonly List Requests = []; + public SpeedLinesOverlay? Overlay; + } + + private static readonly Dictionary States = []; + + public static SpeedLinesRequest AddRequest(Camera camera) + { + if (!States.TryGetValue(camera, out var state)) + { + state = new CameraState(); + States[camera] = state; + } + + if (!state.Overlay) + { + state.Overlay = camera.gameObject.AddComponent(); + state.Overlay.Requests = state.Requests; + } + + var request = new SpeedLinesRequest(); + state.Requests.Add(request); + return request; + } + + public static void RemoveRequest(Camera camera, SpeedLinesRequest request) + { + if (!States.TryGetValue(camera, out var state)) + return; + + state.Requests.Remove(request); + if (state.Requests.Count == 0) + { + if (state.Overlay) + Object.Destroy(state.Overlay); + States.Remove(camera); + } + } + + internal sealed class SpeedLinesOverlay : MonoBehaviour + { + private const string ShaderAssetPath = "Assets/Shaders/BopVisualEffects_SpeedLines.shader"; + private static Material? _material; + private static bool _shaderUnavailable; + internal List? Requests; + + private static Material? GetMaterial() + { + if (_material) + return _material; + if (_shaderUnavailable) + return null; + + var bundle = ShaderBundleLoader.GetBundle(); + var shader = bundle?.LoadAsset(ShaderAssetPath); + if (shader is null || !shader.isSupported) + { + _shaderUnavailable = true; + ClassLogger.GetForClass().Warning("[Speed Lines] Shader bundle unavailable or shader unsupported."); + return null; + } + + _material = new Material(shader) { hideFlags = HideFlags.HideAndDontSave }; + return _material; + } + + private void OnRenderImage(RenderTexture source, RenderTexture destination) + { + if (Requests is null) + { + Graphics.Blit(source, destination); + return; + } + + SpeedLinesRequest? active = null; + foreach (var request in Requests) + { + if (request.Alpha > 0f && (active is null || request.Alpha > active.Alpha)) + active = request; + } + + var material = GetMaterial(); + if (active is null || material is null) + { + Graphics.Blit(source, destination); + return; + } + + material.SetColor("_Colour", active.Color); + material.SetFloat("_SpeedLinesTiling", Mathf.Clamp(active.Count * 2f, 24f, 384f)); + material.SetFloat("_SpeedLinesAnimation", Mathf.Max(0.1f, active.Speed)); + material.SetFloat("_Reach", Mathf.Clamp01(active.Reach)); + material.SetFloat("_Intensity", Mathf.Clamp01(active.Alpha)); + Graphics.Blit(source, destination, material); + } + } +} diff --git a/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs b/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs index 04209fc..f4ead4a 100644 --- a/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs +++ b/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs @@ -6,7 +6,7 @@ namespace BopVisualEffects.Patches; /// -/// Stops all active visual effect runners when editor playback is stopped/paused. +/// Stops all active visual effect runners when editor playback is stopped. /// [HarmonyPatch] public static class MixtapeEditorScriptPlaybackPatch @@ -20,10 +20,6 @@ public static IEnumerable TargetMethods() if (playOrStop is not null) yield return playOrStop; - MethodBase? pauseOrUnpause = AccessTools.Method(typeof(MixtapeEditorScript), "PauseOrUnpauseMixtape"); - if (pauseOrUnpause is not null) - yield return pauseOrUnpause; - MethodBase? filePause = AccessTools.Method(typeof(MixtapeEditorScript), "FilePause"); if (filePause is not null) yield return filePause; diff --git a/BopVisualEffects/Resources/bopvisualeffects_shaders_linux.assetbundle b/BopVisualEffects/Resources/bopvisualeffects_shaders_linux.assetbundle index ade2e4e8cb895e1f612090ef95a39ecf845513a7..e28191854d1117a74b468e4d89a7c633cbd0cae9 100644 GIT binary patch delta 14779 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 V%b!ht?I2(5r0gOdk^kwZ=fO$JysQ8K delta 10612 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 uh|my;h#%7WvGUH|wPcL5aM8nzPv diff --git a/BopVisualEffectsShaders/Assets/Editor/BuildAssetBundles.cs b/BopVisualEffectsShaders/Assets/Editor/BuildAssetBundles.cs index 19bbd3a..016c8ef 100644 --- a/BopVisualEffectsShaders/Assets/Editor/BuildAssetBundles.cs +++ b/BopVisualEffectsShaders/Assets/Editor/BuildAssetBundles.cs @@ -37,6 +37,7 @@ public static void Build() { "Assets/Shaders/BopVisualEffects_HSL.shader", "Assets/Shaders/BopVisualEffects_Sepia.shader", + "Assets/Shaders/BopVisualEffects_SpeedLines.shader", } } }; diff --git a/BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader b/BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader new file mode 100644 index 0000000..98569c9 --- /dev/null +++ b/BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader @@ -0,0 +1,88 @@ +Shader "Hidden/BopVisualEffects_SpeedLines" +{ + Properties + { + _MainTex ("Texture", 2D) = "white" {} + _Colour ("Colour", Color) = (1,1,1,1) + _SpeedLinesTiling ("Speed Lines Tiling", Float) = 192 + _SpeedLinesAnimation ("Speed Lines Animation", Float) = 3 + _SpeedLinesRemap ("Speed Lines Remap", Range(0,1)) = 0.65 + _SpeedLinesPower ("Speed Lines Power", Float) = 1 + _Reach ("Reach", Range(0,1)) = 0.25 + _Intensity ("Intensity", Range(0,1)) = 1 + } + SubShader + { + ZTest Always Cull Off ZWrite Off + Pass + { + CGPROGRAM + #pragma vertex vert_img + #pragma fragment frag + #include "UnityCG.cginc" + + sampler2D _MainTex; + float4 _Colour; + float _SpeedLinesTiling; + float _SpeedLinesAnimation; + float _SpeedLinesRemap; + float _SpeedLinesPower; + float _Reach; + float _Intensity; + + float3 mod289(float3 x) { return x - floor(x / 289.0) * 289.0; } + float2 mod289(float2 x) { return x - floor(x / 289.0) * 289.0; } + float3 permute(float3 x) { return mod289((x * 34.0 + 1.0) * x); } + + float snoise(float2 v) + { + const float4 C = float4(0.2113248654, 0.3660254038, -0.5773502692, 0.0243902439); + float2 i = floor(v + dot(v, C.yy)); + float2 x0 = v - i + dot(i, C.xx); + float2 i1 = (x0.x > x0.y) ? float2(1, 0) : float2(0, 1); + float4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + i = mod289(i); + float3 p = permute(permute(i.y + float3(0, i1.y, 1)) + i.x + float3(0, i1.x, 1)); + float3 m = max(0.5 - float3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0); + m *= m; m *= m; + float3 x = 2.0 * frac(p * C.www) - 1.0; + float3 h = abs(x) - 0.5; + float3 ox = floor(x + 0.5); + float3 a0 = x - ox; + m *= 1.7928429 - 0.8537347 * (a0 * a0 + h * h); + float3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); + } + + fixed4 frag(v2f_img i) : SV_Target + { + float2 centered = i.uv - 0.5; + float aspect = _ScreenParams.x / _ScreenParams.y; + float2 aspectCorrected = float2(centered.x * aspect, centered.y); + float radial = length(aspectCorrected) * 2.0; + float angle = atan2(centered.x, centered.y) / 6.28318530718; + + float2 noiseUv = float2(radial * 0.2, angle * _SpeedLinesTiling); + noiseUv.x -= _SpeedLinesAnimation * _Time.y; + float noise = snoise(noiseUv) * 0.5 + 0.5; + float lines = saturate((pow(noise, _SpeedLinesPower) - _SpeedLinesRemap) / max(0.001, 1.0 - _SpeedLinesRemap)); + + // Use normalized distance to the nearest rectangular screen edge for the + // reach mask. The radial coordinate can exceed 1 on a widescreen display; + // masking it as a circle would incorrectly remove the side edges. + float edgeDistance = max(abs(centered.x), abs(centered.y)) * 2.0; + float innerRadius = saturate(1.0 - _Reach); + float mask = smoothstep(innerRadius, min(1.0, innerRadius + 0.12), edgeDistance); + float effect = lines * mask * _Intensity * _Colour.a; + + fixed4 scene = tex2D(_MainTex, i.uv); + scene.rgb = lerp(scene.rgb, _Colour.rgb, effect); + return scene; + } + ENDCG + } + } +} diff --git a/BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader.meta b/BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader.meta new file mode 100644 index 0000000..e51ef30 --- /dev/null +++ b/BopVisualEffectsShaders/Assets/Shaders/BopVisualEffects_SpeedLines.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2e41f2f4fbe8472d9a2c338da5a1b0fe +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/BopVisualEffectsShaders/README.md b/BopVisualEffectsShaders/README.md index db03d72..0b7821d 100644 --- a/BopVisualEffectsShaders/README.md +++ b/BopVisualEffectsShaders/README.md @@ -31,6 +31,7 @@ to their GL-based rendering path. |---|---| | `Assets/Shaders/BopVisualEffects_HSL.shader` | Per-pixel hue rotation, saturation scaling and lightness offset. | | `Assets/Shaders/BopVisualEffects_Sepia.shader` | Per-pixel sepia tone using the standard Adobe/Kodak conversion matrix. | +| `Assets/Shaders/BopVisualEffects_SpeedLines.shader` | Polar-noise radial speed-line post-processing effect based on Anime-Speed-Lines. | ## Build steps diff --git a/docs/effects/README.md b/docs/effects/README.md index 584b74c..c948b4b 100644 --- a/docs/effects/README.md +++ b/docs/effects/README.md @@ -192,12 +192,14 @@ https://github.com/user-attachments/assets/ff1025b3-1f4b-44c8-982f-6691fe861277 **Config Key:** `SpeedLines.Enabled` -Draws animated black triangles radiating inward from the screen edges to convey a feeling of high speed. Each triangle independently flickers at its own phase, creating a dynamic rush effect. Fades in and out smoothly. +Draws an animated, noise-driven radial speed-line vignette. Thin colored streak segments are procedurally distributed in polar space and continuously scroll toward the screen edges. Fades in and out smoothly. **Properties** -- `alpha`: Maximum opacity of the triangles (0–1; default `0.8`). -- `count`: Number of speed-line triangles (4–64; default `20`). -- `speed`: Flicker rate of each triangle in cycles per second (default `3.5`). Higher values produce more frantic flickering; lower values give slower, more rhythmic pulses. +- `alpha`: Maximum opacity of the speed lines (0–1; default `0.8`). +- `count`: Angular density of the procedural speed-line field (24–192; default `96`). +- `speed`: Animation rate of the procedural noise in cycles per second (default `3.5`). Higher values produce faster-moving streaks. +- `reach`: How far each speed line reaches toward the screen center as a normalized edge-to-center fraction (0–1; default `0.25`). `0` keeps the effect at the screen edge; `1` reaches the center. The calculation accounts for the screen aspect ratio. +- `color`: Color and base opacity of the speed lines (default white). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** From 59094528e22ccacbe404208f8afc4e9daea67f42 Mon Sep 17 00:00:00 2001 From: Brollyy Date: Mon, 7 Sep 2026 23:48:11 +0200 Subject: [PATCH 07/10] feat: add speed lines easing curve --- .../Core/EntityCurveExtensions.cs | 21 +++++++++++++++ .../Effects/SpeedLines/SpeedLinesEffect.cs | 26 +++++++++++-------- docs/effects/README.md | 1 + 3 files changed, 37 insertions(+), 11 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..948aa40 --- /dev/null +++ b/BopVisualEffects/Core/EntityCurveExtensions.cs @@ -0,0 +1,21 @@ +using UnityEngine; + +namespace BopVisualEffects.Core; + +/// +/// Helpers for reading animation curves from event properties. +/// +internal static class EntityCurveExtensions +{ + /// + /// Returns the curve stored under , or + /// when the property is absent or has an unexpected type. + /// + internal static AnimationCurve GetAnimationCurve(this Entity entity, string key, AnimationCurve fallback) + { + if (entity.dynamicData.TryGetValue(key, out var value) && value is AnimationCurve curve) + return curve; + + return fallback; + } +} diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs index 1c9535d..5d48dcc 100644 --- a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs @@ -38,7 +38,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) ["count"] = 96.0f, ["speed"] = 3.5f, ["reach"] = DefaultReach, - ["color"] = new MixtapeEventTemplates.ColorField(Color.white) + ["color"] = new MixtapeEventTemplates.ColorField(Color.white), + ["easing_curve"] = DefaultEasingCurve() } }; } @@ -53,6 +54,7 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var speed = entity.GetFloat("speed"); var reach = entity.GetFloat("reach"); var color = entity.GetColor("color"); + var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; loader.scheduler.Schedule(startBeat, (System.Action?)SpawnAction); @@ -62,10 +64,17 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, speed, reach, color)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, speed, reach, color, 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 SpeedLinesRunner : MonoBehaviour { private bool _initialized; @@ -80,11 +89,12 @@ private sealed class SpeedLinesRunner : MonoBehaviour private JukeboxScript? _jukebox; private Camera? _camera; private SpeedLinesRequest? _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 alpha, float count, float speed, float reach, Color color) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float speed, float reach, Color color, AnimationCurve easingCurve) { _loader = loader; _jukebox = jukebox; @@ -95,6 +105,7 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _speed = Mathf.Max(0.1f, speed); _reach = Mathf.Clamp01(reach); _color = new Color(Mathf.Clamp01(color.r), Mathf.Clamp01(color.g), Mathf.Clamp01(color.b), Mathf.Clamp01(color.a)); + _easingCurve = easingCurve; InitializeOverlay(); } @@ -129,15 +140,8 @@ private void LateUpdate() return; } - // 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 (_request != null) _request.Alpha = _alpha * envelope; diff --git a/docs/effects/README.md b/docs/effects/README.md index c948b4b..db3d952 100644 --- a/docs/effects/README.md +++ b/docs/effects/README.md @@ -200,6 +200,7 @@ Draws an animated, noise-driven radial speed-line vignette. Thin colored streak - `speed`: Animation rate of the procedural noise in cycles per second (default `3.5`). Higher values produce faster-moving streaks. - `reach`: How far each speed line reaches toward the screen center as a normalized edge-to-center fraction (0–1; default `0.25`). `0` keeps the effect at the screen edge; `1` reaches the center. The calculation accounts for the screen aspect ratio. - `color`: Color and base opacity of the speed lines (default white). +- `easing_curve`: Animation curve controlling speed-line opacity over the event duration (default: fade in over 15%, hold, then fade out over 15%). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** From e9a1ee6cc33e7f596598cb7aa095b128a8c34aeb Mon Sep 17 00:00:00 2001 From: Brollyy Date: Mon, 7 Sep 2026 23:59:09 +0200 Subject: [PATCH 08/10] fix: use explicit speed lines easing toggles --- .../Core/EntityCurveExtensions.cs | 21 ---------------- .../Effects/SpeedLines/SpeedLinesEffect.cs | 25 ++++++++----------- docs/effects/README.md | 3 ++- 3 files changed, 13 insertions(+), 36 deletions(-) delete mode 100644 BopVisualEffects/Core/EntityCurveExtensions.cs diff --git a/BopVisualEffects/Core/EntityCurveExtensions.cs b/BopVisualEffects/Core/EntityCurveExtensions.cs deleted file mode 100644 index 948aa40..0000000 --- a/BopVisualEffects/Core/EntityCurveExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using UnityEngine; - -namespace BopVisualEffects.Core; - -/// -/// Helpers for reading animation curves from event properties. -/// -internal static class EntityCurveExtensions -{ - /// - /// Returns the curve stored under , or - /// when the property is absent or has an unexpected type. - /// - internal static AnimationCurve GetAnimationCurve(this Entity entity, string key, AnimationCurve fallback) - { - if (entity.dynamicData.TryGetValue(key, out var value) && value is AnimationCurve curve) - return curve; - - return fallback; - } -} diff --git a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs index 5d48dcc..573a56a 100644 --- a/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs +++ b/BopVisualEffects/Effects/SpeedLines/SpeedLinesEffect.cs @@ -39,7 +39,8 @@ public MixtapeEventTemplate CreateTemplate(string pluginGuid) ["speed"] = 3.5f, ["reach"] = DefaultReach, ["color"] = new MixtapeEventTemplates.ColorField(Color.white), - ["easing_curve"] = DefaultEasingCurve() + ["ease_in"] = true, + ["ease_out"] = true } }; } @@ -54,7 +55,8 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) var speed = entity.GetFloat("speed"); var reach = entity.GetFloat("reach"); var color = entity.GetColor("color"); - var easingCurve = entity.GetAnimationCurve("easing_curve", DefaultEasingCurve()); + var easeIn = entity.GetBool("ease_in", true); + var easeOut = entity.GetBool("ease_out", true); var startBeat = entity.beat; var endBeat = startBeat + durationBeats; loader.scheduler.Schedule(startBeat, (System.Action?)SpawnAction); @@ -64,17 +66,10 @@ public bool TrySchedule(Entity entity, MixtapeLoaderCustom loader) void SpawnAction() { EffectRuntimeController.Instance.SpawnRunner(runner => - runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, speed, reach, color, easingCurve)); + runner.Initialize(loader, loader.jukebox, startBeat, endBeat, alpha, count, speed, reach, color, easeIn, easeOut)); } } - 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 SpeedLinesRunner : MonoBehaviour { private bool _initialized; @@ -89,12 +84,13 @@ private sealed class SpeedLinesRunner : MonoBehaviour private JukeboxScript? _jukebox; private Camera? _camera; private SpeedLinesRequest? _request; - private AnimationCurve _easingCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f); + 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 speed, float reach, Color color, AnimationCurve easingCurve) + public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float startBeat, float endBeat, float alpha, float count, float speed, float reach, Color color, bool easeIn, bool easeOut) { _loader = loader; _jukebox = jukebox; @@ -105,7 +101,8 @@ public void Initialize(MixtapeLoaderCustom loader, JukeboxScript? jukebox, float _speed = Mathf.Max(0.1f, speed); _reach = Mathf.Clamp01(reach); _color = new Color(Mathf.Clamp01(color.r), Mathf.Clamp01(color.g), Mathf.Clamp01(color.b), Mathf.Clamp01(color.a)); - _easingCurve = easingCurve; + _easeIn = easeIn; + _easeOut = easeOut; InitializeOverlay(); } @@ -141,7 +138,7 @@ private void LateUpdate() } var progress = Mathf.InverseLerp(_startBeat, _endBeat, currentBeat); - var envelope = _easingCurve.Evaluate(progress); + var envelope = EffectEnvelope.Evaluate(progress, _easeIn, _easeOut, 0.15f, 0.85f); if (_request != null) _request.Alpha = _alpha * envelope; diff --git a/docs/effects/README.md b/docs/effects/README.md index 9d334d6..5dda346 100644 --- a/docs/effects/README.md +++ b/docs/effects/README.md @@ -216,7 +216,8 @@ Draws an animated, noise-driven radial speed-line vignette. Thin colored streak - `speed`: Animation rate of the procedural noise in cycles per second (default `3.5`). Higher values produce faster-moving streaks. - `reach`: How far each speed line reaches toward the screen center as a normalized edge-to-center fraction (0–1; default `0.25`). `0` keeps the effect at the screen edge; `1` reaches the center. The calculation accounts for the screen aspect ratio. - `color`: Color and base opacity of the speed lines (default white). -- `easing_curve`: Animation curve controlling speed-line opacity over the event duration (default: fade in over 15%, hold, then fade out over 15%). +- `ease_in`: Fade speed lines in over the first 15% of the event (default `true`). +- `ease_out`: Fade speed lines out over the last 15% of the event (default `true`). - `length` (event length in editor): How long the effect lasts, in beats. This event is resizable in the timeline. **Preview** From 14bed615777777bf637e9108462808c55b339c1d Mon Sep 17 00:00:00 2001 From: Brollyy Date: Tue, 8 Sep 2026 00:19:29 +0200 Subject: [PATCH 09/10] fix: restore pause playback cleanup --- BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs b/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs index f4ead4a..f8a9478 100644 --- a/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs +++ b/BopVisualEffects/Patches/MixtapeEditorScriptPlaybackPatch.cs @@ -23,6 +23,10 @@ public static IEnumerable TargetMethods() MethodBase? filePause = AccessTools.Method(typeof(MixtapeEditorScript), "FilePause"); if (filePause is not null) yield return filePause; + + MethodBase? pauseOrUnpause = AccessTools.Method(typeof(MixtapeEditorScript), "PauseOrUnpauseMixtape"); + if (pauseOrUnpause is not null) + yield return pauseOrUnpause; } /// From 228f51f9090a8c44c733b25db8bf22f078124bd4 Mon Sep 17 00:00:00 2001 From: Brollyy Date: Tue, 8 Sep 2026 00:24:42 +0200 Subject: [PATCH 10/10] Update README.md --- docs/effects/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/effects/README.md b/docs/effects/README.md index 5dda346..8728d23 100644 --- a/docs/effects/README.md +++ b/docs/effects/README.md @@ -222,7 +222,7 @@ Draws an animated, noise-driven radial speed-line vignette. Thin colored streak **Preview** - +https://github.com/user-attachments/assets/edddc3f0-6b81-4b29-9956-da7df65da25b ## Vertical Flip