From 1818c1b1e6ed5de2346aedfb0d30a276d55b6f7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 May 2026 17:51:19 +0900 Subject: [PATCH] Fix pause ambience loop not playing at fail screen (#37663) Matches stable. --- Addresses https://github.com/ppy/osu/discussions/37580. --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 62 +++++++++++++++++++- osu.Game/Screens/Play/PauseOverlay.cs | 60 ------------------- osu.Game/Screens/Play/Player.cs | 1 + 3 files changed, 61 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index d4c40c78ae..12f0937eb5 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,6 +15,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Platform; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -23,6 +27,7 @@ using osuTK; using osuTK.Graphics; using osu.Game.Localisation; using osu.Game.Resources.Localisation.Web; +using osu.Game.Skinning; using osu.Game.Utils; namespace osu.Game.Screens.Play @@ -76,10 +81,15 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, GameHost? host) { Children = new Drawable[] { + pauseLoop = new SkinnableSound(new SampleInfo("Gameplay/pause-loop")) + { + Looping = true, + Volume = { Value = 0 } + }, new Box { RelativeSizeAxes = Axes.Both, @@ -142,6 +152,9 @@ namespace osu.Game.Screens.Play State.ValueChanged += _ => InternalButtons.Deselect(); updateInfoText(); + + if (host != null) + windowActive.BindTo(host.IsActive); } private int retries; @@ -164,9 +177,15 @@ namespace osu.Game.Screens.Play { this.FadeIn(TRANSITION_DURATION, Easing.In); updateInfoText(); + + startPauseLoop(); } - protected override void PopOut() => this.FadeOut(TRANSITION_DURATION, Easing.In); + protected override void PopOut() + { + this.FadeOut(TRANSITION_DURATION, Easing.In); + stopPauseLoop(); + } protected void AddButton(LocalisableString text, Color4 colour, Action? action) { @@ -283,5 +302,44 @@ namespace osu.Game.Screens.Play return base.Handle(e); } + + #region Pause loop sound handling + + public override bool IsPresent => base.IsPresent || pauseLoop.IsPlaying; + + private SkinnableSound pauseLoop = null!; + + private readonly IBindable windowActive = new Bindable(true); + + private float targetVolume => windowActive.Value && State.Value == Visibility.Visible ? 1.0f : 0; + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Schedule required because host.IsActive doesn't seem to always run on the update thread. + windowActive.BindValueChanged(_ => Schedule(() => pauseLoop.VolumeTo(targetVolume, 1000, Easing.Out))); + } + + public void StopAllSamples() + { + if (!IsLoaded) + return; + + pauseLoop.Stop(); + } + + private void startPauseLoop() + { + pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.InQuint); + pauseLoop.Play(); + } + + private void stopPauseLoop() + { + pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); + } + + #endregion } } diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 18d17c1317..7e429d1e21 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -3,29 +3,17 @@ using System; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Framework.Platform; -using osu.Game.Audio; using osu.Game.Input.Bindings; using osu.Game.Localisation; -using osu.Game.Skinning; namespace osu.Game.Screens.Play { public partial class PauseOverlay : GameplayMenuOverlay { - public override bool IsPresent => base.IsPresent || pauseLoop.IsPlaying; - public override LocalisableString Header => GameplayMenuOverlayStrings.PausedHeader; - private SkinnableSound pauseLoop = null!; - protected override Action BackAction => () => { if (Buttons.Any()) @@ -34,54 +22,6 @@ namespace osu.Game.Screens.Play OnResume?.Invoke(); }; - private readonly IBindable windowActive = new Bindable(true); - - private float targetVolume => windowActive.Value && State.Value == Visibility.Visible ? 1.0f : 0; - - [BackgroundDependencyLoader] - private void load(GameHost? host) - { - AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("Gameplay/pause-loop")) - { - Looping = true, - Volume = { Value = 0 } - }); - - if (host != null) - windowActive.BindTo(host.IsActive); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // Schedule required because host.IsActive doesn't seem to always run on the update thread. - windowActive.BindValueChanged(_ => Schedule(() => pauseLoop.VolumeTo(targetVolume, 1000, Easing.Out))); - } - - public void StopAllSamples() - { - if (!IsLoaded) - return; - - pauseLoop.Stop(); - } - - protected override void PopIn() - { - base.PopIn(); - - pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.InQuint); - pauseLoop.Play(); - } - - protected override void PopOut() - { - base.PopOut(); - - pauseLoop.VolumeTo(targetVolume, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); - } - public override bool OnPressed(KeyBindingPressEvent e) { switch (e.Action) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ff62db7e16..4bde0cbd55 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1194,6 +1194,7 @@ namespace osu.Game.Screens.Play // Eagerly clean these up as disposal of child components is asynchronous and may leave sounds playing beyond user expectations. failAnimationContainer?.Stop(); PauseOverlay?.StopAllSamples(); + FailOverlay?.StopAllSamples(); if (LoadedBeatmapSuccessfully && !GameplayState.HasPassed) {