1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-27 21:40:42 +08:00

Implement background music for ranked play (#37166)

Using a bespoke music loop, fades in during the intro:


https://github.com/user-attachments/assets/8db7e30c-2a7c-4fd0-8385-fa77a6e0a506

Ducks when hovering cards for previews, with debouncing to smooth it:


https://github.com/user-attachments/assets/b3557c08-c675-4591-963c-95866d45107f

---
- [x] depends on ppy/osu-resources#414

---------

Co-authored-by: Dean Herbert <pe@ppy.sh>
This commit is contained in:
Jamie Taylor
2026-04-02 17:12:25 +09:00
committed by GitHub
Unverified
parent 2d85966074
commit c4f2c66fd6
6 changed files with 125 additions and 21 deletions
+4
View File
@@ -22,6 +22,8 @@ namespace osu.Game.Audio
protected TrackManagerPreviewTrack? CurrentTrack;
public readonly BindableBool IsPlayingPreview = new BindableBool();
public PreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments)
{
this.mainTrackAdjustments = mainTrackAdjustments;
@@ -47,6 +49,7 @@ namespace osu.Game.Audio
CurrentTrack?.Stop();
CurrentTrack = track;
mainTrackAdjustments.AddAdjustment(AdjustableProperty.Volume, muteBindable);
IsPlayingPreview.Value = true;
});
track.Stopped += () => Schedule(() =>
@@ -56,6 +59,7 @@ namespace osu.Game.Audio
CurrentTrack = null;
mainTrackAdjustments.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
IsPlayingPreview.Value = false;
});
return track;
@@ -41,6 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Card
protected override Container<Drawable> Content { get; }
private readonly Bindable<bool> trackRunning = new BindableBool();
private readonly Container overlayLayer;
private bool shouldBePlaying => Enabled.Value && IsHovered;
@@ -114,7 +115,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Card
overlayLayer.Add(new RippleVisualization(cardColours.Border)
{
TrackRunning = trackRunning.GetBoundCopy(),
TrackRunning = { BindTarget = trackRunning }
});
if (IsHovered)
@@ -124,12 +125,25 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Card
protected override bool OnHover(HoverEvent e)
{
if (previewTrack != null)
previewTrack.Looping = true;
if (shouldBePlaying)
{
startPreviewIfAvailable();
}
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
if (previewTrack != null)
previewTrack.Looping = false;
base.OnHoverLost(e);
}
private void onTrackStarted() => Schedule(() => trackRunning.Value = true);
private void onTrackStopped() => Schedule(() => trackRunning.Value = false);
@@ -193,7 +207,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Card
[Resolved]
private SongPreviewParticleContainer? particleContainer { get; set; }
public required IBindable<bool> TrackRunning { get; init; }
public readonly IBindable<bool> TrackRunning = new Bindable<bool>();
private readonly Color4 accentColour;
private readonly Container rippleContainer;
@@ -0,0 +1,93 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
using osu.Game.Audio;
using osu.Game.Overlays;
namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components
{
public partial class BackgroundMusicManager : CompositeComponent
{
private const int hover_fade_duration = 250;
private ScheduledDelegate? globalTrackFadeDelegate;
private DrawableTrack bgm = null!;
private Bindable<bool> isPlayingPreview = null!;
[Resolved]
private MusicController musicController { get; set; } = null!;
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuGameBase game)
{
AddInternal(bgm = new DrawableTrack(audio.Tracks.Get("rankedplay_bgm.ogg")));
}
protected override void LoadComplete()
{
base.LoadComplete();
isPlayingPreview = previewTrackManager.IsPlayingPreview.GetBoundCopy();
isPlayingPreview.BindValueChanged(playing =>
{
bgm.VolumeTo(playing.NewValue ? 0 : 1, hover_fade_duration);
});
}
public void Play()
{
if (bgm.IsRunning)
return;
const int track_fade_duration = 3000;
// remove music control from player, to prevent overlapping music
musicController.AllowTrackControl.Value = false;
globalTrackFadeDelegate?.Cancel();
// cross-fade if global track is playing something
if (musicController.IsPlaying)
{
var globalTrack = musicController.CurrentTrack;
globalTrack.VolumeTo(0, track_fade_duration, Easing.OutCubic);
globalTrackFadeDelegate = Scheduler.AddDelayed(() =>
{
musicController.Stop();
globalTrack.VolumeTo(1);
}, track_fade_duration);
}
bgm.VolumeTo(0)
.VolumeTo(1, track_fade_duration, Easing.InCubic);
bgm.Looping = true;
bgm.Start();
}
public void Stop()
{
globalTrackFadeDelegate?.Cancel();
bgm.Stop();
bgm.Reset();
// return control of music to player and reset volume
musicController.AllowTrackControl.Value = true;
musicController.CurrentTrack.Volume.Value = 1;
musicController.EnsurePlayingSomething();
}
}
}
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
@@ -15,7 +14,6 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
using osu.Game.Overlays;
namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Intro
{
@@ -36,9 +34,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Intro
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private MusicController? musicController { get; set; }
private Sample? windupSample;
private Sample? impactSample;
private Sample? swooshSample;
@@ -81,8 +76,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Intro
private StarRatingSequence? starRatingAnimation;
private IDisposable? duckOperation;
public void PlayIntroSequence(UserWithRating player, UserWithRating opponent, double starRating)
{
double delay = 0;
@@ -95,8 +88,6 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Intro
vsScreen.Play(ref delay, out double impactDelay);
duckOperation = musicController?.Duck();
if (windupSample != null)
{
Scheduler.AddDelayed(() => windupSample?.Play(), impactDelay - windupSample.Length);
@@ -113,16 +104,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Intro
{
starRatingAnimation?.PopOut();
duckOperation?.Dispose();
this.Delay(500).FadeOut();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
duckOperation?.Dispose();
}
}
}
@@ -88,6 +88,9 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
[Cached]
private readonly SongPreviewParticleContainer particleContainer;
[Cached]
private BackgroundMusicManager backgroundMusic;
public RankedPlayScreen(MultiplayerRoom room)
{
this.room = room;
@@ -129,6 +132,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
},
overlayContainer = new CardDetailsOverlayContainer(),
particleContainer = new SongPreviewParticleContainer(),
backgroundMusic = new BackgroundMusicManager()
};
}
@@ -224,6 +228,11 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
{
chat.Appear();
if (stage is RankedPlayStage.GameplayWarmup or RankedPlayStage.Gameplay)
backgroundMusic.Stop();
else
backgroundMusic.Play();
switch (stage)
{
case RankedPlayStage.RoundWarmup when matchInfo.CurrentRound == 1:
@@ -278,6 +287,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
public override void OnSuspending(ScreenTransitionEvent e)
{
chat.Disappear();
backgroundMusic.Stop();
previewTrackManager.StopAnyPlaying(this);
base.OnSuspending(e);
@@ -296,6 +306,7 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay
return true;
}
backgroundMusic.Stop();
previewTrackManager.StopAnyPlaying(this);
client.LeaveRoom().FireAndForget();
+1 -1
View File
@@ -40,7 +40,7 @@
</PackageReference>
<PackageReference Include="Realm" Version="20.1.0" />
<PackageReference Include="ppy.osu.Framework" Version="2026.318.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2026.331.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2026.402.0" />
<PackageReference Include="Sentry" Version="6.2.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.47.0" />