From d5074bb30f785eaada7de720959dab84a6528702 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 14 Mar 2025 20:47:11 +0900 Subject: [PATCH 1/4] 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 From 533b0e0f887553f107d35257c136ba36344958be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 16:35:11 +0900 Subject: [PATCH 2/4] Allow testing fountain sound effects in tests --- .../Visual/Menus/TestSceneStarFountain.cs | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 0d981014b8..396d2e9027 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -50,30 +50,17 @@ namespace osu.Game.Tests.Visual.Menus [Test] public void TestGameplay() { + KiaiGameplayFountains fountains = null!; + AddStep("make fountains", () => { Children = new[] { - new KiaiGameplayFountains.GameplayStarFountain - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = 75, - }, - new KiaiGameplayFountains.GameplayStarFountain - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - X = -75, - }, + fountains = new KiaiGameplayFountains(), }; }); - AddStep("activate fountains", () => - { - ((StarFountain)Children[0]).Shoot(1); - ((StarFountain)Children[1]).Shoot(-1); - }); + AddStep("activate fountains", () => fountains.Shoot()); } [Test] From a4ced55640bdf3ed9846750a4c50e1da9bb65a1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 16:49:27 +0900 Subject: [PATCH 3/4] Code quality pass --- osu.Game/Screens/Menu/KiaiMenuFountains.cs | 6 +- osu.Game/Screens/Menu/StarFountainSfx.cs | 74 ------------------- osu.Game/Screens/Menu/StarFountainSounds.cs | 71 ++++++++++++++++++ .../Screens/Play/KiaiGameplayFountains.cs | 6 +- 4 files changed, 77 insertions(+), 80 deletions(-) delete mode 100644 osu.Game/Screens/Menu/StarFountainSfx.cs create mode 100644 osu.Game/Screens/Menu/StarFountainSounds.cs diff --git a/osu.Game/Screens/Menu/KiaiMenuFountains.cs b/osu.Game/Screens/Menu/KiaiMenuFountains.cs index 7baf18d526..6e0351f922 100644 --- a/osu.Game/Screens/Menu/KiaiMenuFountains.cs +++ b/osu.Game/Screens/Menu/KiaiMenuFountains.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Menu [Resolved] private GameHost host { get; set; } = null!; - private StarFountainSfx sfx = null!; + private StarFountainSounds sounds = null!; [BackgroundDependencyLoader] private void load() @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.BottomRight, X = -250, }, - sfx = new StarFountainSfx() + sounds = new StarFountainSounds() }; } @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Menu // Don't play SFX when game is in background, as it can be a bit noisy. if (host.IsActive.Value) - sfx.Trigger(); + sounds.Play(); } } } diff --git a/osu.Game/Screens/Menu/StarFountainSfx.cs b/osu.Game/Screens/Menu/StarFountainSfx.cs deleted file mode 100644 index 91337d6959..0000000000 --- a/osu.Game/Screens/Menu/StarFountainSfx.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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/Menu/StarFountainSounds.cs b/osu.Game/Screens/Menu/StarFountainSounds.cs new file mode 100644 index 0000000000..842e718c48 --- /dev/null +++ b/osu.Game/Screens/Menu/StarFountainSounds.cs @@ -0,0 +1,71 @@ +// 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 StarFountainSounds : CompositeComponent + { + private const int shoot_retrigger_delay = 500; + private const int loop_fade_duration = 500; + + private double? lastPlayback; + + private SkinnableSound shootSample = null!; + private PausableSkinnableSound loopSample = null!; + + private ScheduledDelegate? loopFadeDelegate; + private ScheduledDelegate? loopStopDelegate; + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + shootSample = new SkinnableSound(new SampleInfo("Gameplay/fountain-shoot")), + loopSample = new PausableSkinnableSound(new SampleInfo("Gameplay/fountain-loop")) { Looping = true }, + }; + } + + public void Play() + { + loopFadeDelegate?.Cancel(); + loopStopDelegate?.Cancel(); + + try + { + // Only play 'shootSample' if enough time has passed since last `Play()` call. + if (lastPlayback == null || Time.Current - lastPlayback > shoot_retrigger_delay) + { + loopSample.Stop(); + shootSample.Play(); + return; + } + + // Only call `Play()` if `loopSample` is not already playing, to prevent restarting the sample each time. + if (!loopSample.RequestedPlaying) + { + this.TransformBindableTo(loopSample.Volume, 1); + 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); + } + finally + { + lastPlayback = Time.Current; + } + } + } +} diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index cdeb2a0700..826c60c6cf 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play private Bindable kiaiStarFountains = null!; - private StarFountainSfx sfx = null!; + private StarFountainSounds sounds = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomRight, X = -75, }, - sfx = new StarFountainSfx(), + sounds = new StarFountainSounds(), }; } @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Play leftFountain.Shoot(1); rightFountain.Shoot(-1); - sfx.Trigger(); + sounds.Play(); } public partial class GameplayStarFountain : StarFountain From 2be450c01016680ee1eed81921f179c4349884d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Mar 2025 17:10:13 +0900 Subject: [PATCH 4/4] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5b5482b3c7..438864f873 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - +