From d5074bb30f785eaada7de720959dab84a6528702 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 14 Mar 2025 20:47:11 +0900 Subject: [PATCH] Improve SFX playback behaviour of rapid kiai/star fountain activations --- osu.Game/Screens/Menu/KiaiMenuFountains.cs | 10 +-- osu.Game/Screens/Menu/StarFountainSfx.cs | 74 +++++++++++++++++++ .../Screens/Play/KiaiGameplayFountains.cs | 8 +- 3 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Screens/Menu/StarFountainSfx.cs diff --git a/osu.Game/Screens/Menu/KiaiMenuFountains.cs b/osu.Game/Screens/Menu/KiaiMenuFountains.cs index b57012eaf7..7baf18d526 100644 --- a/osu.Game/Screens/Menu/KiaiMenuFountains.cs +++ b/osu.Game/Screens/Menu/KiaiMenuFountains.cs @@ -6,9 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Graphics.Containers; -using osu.Game.Skinning; namespace osu.Game.Screens.Menu { @@ -20,7 +18,7 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } = null!; - private SkinnableSound? sample; + private StarFountainSfx sfx = null!; [BackgroundDependencyLoader] private void load() @@ -41,7 +39,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.BottomRight, X = -250, }, - sample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")) + sfx = new StarFountainSfx() }; } @@ -83,9 +81,9 @@ namespace osu.Game.Screens.Menu break; } - // Don't play SFX when game is in background as it can be a bit noisy. + // Don't play SFX when game is in background, as it can be a bit noisy. if (host.IsActive.Value) - sample?.Play(); + sfx.Trigger(); } } } diff --git a/osu.Game/Screens/Menu/StarFountainSfx.cs b/osu.Game/Screens/Menu/StarFountainSfx.cs new file mode 100644 index 0000000000..91337d6959 --- /dev/null +++ b/osu.Game/Screens/Menu/StarFountainSfx.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Threading; +using osu.Game.Audio; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Menu +{ + public partial class StarFountainSfx : Container + { + private const int shoot_retrigger_delay = 500; + private const int loop_fade_duration = 500; + + private double? lastPlayback; + + private SkinnableSound? shootSample; + private PausableSkinnableSound? loopSample; + + private ScheduledDelegate? loopFadeDelegate; + private ScheduledDelegate? loopStopDelegate; + + public void Trigger() + { + loopFadeDelegate?.Cancel(); + loopStopDelegate?.Cancel(); + + // Only play 'shootSample' if enough time has passed since last `Trigger()` call. + if (lastPlayback == null || Time.Current - lastPlayback > shoot_retrigger_delay) + { + loopSample?.Stop(); + shootSample?.Play(); + lastPlayback = Time.Current; + + return; + } + + if (loopSample == null) return; + + // Only call `Play()` if `loopSample` is not already playing, to prevent restarting the sample each time. + if (!loopSample.RequestedPlaying) + { + loopSample.Volume.Value = 1f; + loopSample.Play(); + } + + // Schedule a volume fadeout, followed by a `Stop()`. + loopFadeDelegate = Scheduler.AddDelayed(() => + { + this.TransformBindableTo(loopSample.Volume, 0, loop_fade_duration); + + loopStopDelegate = Scheduler.AddDelayed(() => + { + loopSample?.Stop(); + }, loop_fade_duration); + }, shoot_retrigger_delay); + + lastPlayback = Time.Current; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + shootSample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")), + loopSample = new PausableSkinnableSound(new SampleInfo("Gameplay/fountain-loop")) { Looping = true }, + }; + } + } +} diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 017e66253f..cdeb2a0700 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -6,11 +6,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Screens.Menu; -using osu.Game.Skinning; namespace osu.Game.Screens.Play { @@ -21,7 +19,7 @@ namespace osu.Game.Screens.Play private Bindable kiaiStarFountains = null!; - private SkinnableSound? sample; + private StarFountainSfx sfx = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -44,7 +42,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, X = -75, }, - sample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")) + sfx = new StarFountainSfx(), }; } @@ -72,7 +70,7 @@ namespace osu.Game.Screens.Play leftFountain.Shoot(1); rightFountain.Shoot(-1); - sample?.Play(); + sfx.Trigger(); } public partial class GameplayStarFountain : StarFountain