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

Display beatmap state in RankedPlayUserDisplay (#37188)

This has gone through a few iterations, and eventually ended up as a
simple text percentage display next to the username. I feel that adding
another progress bar right next to the big healthbar would make things
too cluttered, and trying to move the beatmap state elsewhere would make
it too disconnected from the players that are potentially downloading a
beatmap.

I considered making the local user fetch download progress data using
`BeatmapDownloadTracker` instead of relying on `BeatmapAvailability` in
order to get more frequent updates, but that would add a lot of extra
complexity for little gain IMO.


[Screencast_20260403_095644.webm](https://github.com/user-attachments/assets/85fbd4b8-6b5c-41d2-b29b-c93885f73bb3)
This commit is contained in:
Krzysztof Gutkowski
2026-04-04 15:05:28 +02:00
committed by GitHub
Unverified
parent ae955c0589
commit 8b69aa9fdb
2 changed files with 107 additions and 9 deletions
@@ -4,6 +4,8 @@
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay;
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components;
using osu.Game.Tests.Visual.Multiplayer;
@@ -20,11 +22,19 @@ namespace osu.Game.Tests.Visual.RankedPlay
Value = 1_000_000,
};
public TestSceneRankedPlayUserDisplay()
{
AddSliderStep("health", 0, 1_000_000, 1_000_000, value => health.Value = value);
}
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("add display", () => Child = new RankedPlayUserDisplay(2, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.RankedPlay)));
WaitForJoined();
AddStep("add display", () => Child = new RankedPlayUserDisplay(1001, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -36,7 +46,7 @@ namespace osu.Game.Tests.Visual.RankedPlay
[Test]
public void TesUserDisplay()
{
AddStep("blue color scheme", () => Child = new RankedPlayUserDisplay(2, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
AddStep("blue color scheme", () => Child = new RankedPlayUserDisplay(1001, Anchor.BottomLeft, RankedPlayColourScheme.Blue)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -44,15 +54,30 @@ namespace osu.Game.Tests.Visual.RankedPlay
Health = { BindTarget = health }
});
AddStep("red color scheme", () => Child = new RankedPlayUserDisplay(2, Anchor.BottomLeft, RankedPlayColourScheme.Red)
AddStep("red color scheme", () => Child = new RankedPlayUserDisplay(1001, Anchor.BottomLeft, RankedPlayColourScheme.Red)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(256, 72),
Health = { BindTarget = health }
});
}
AddSliderStep("health", 0, 1_000_000, 1_000_000, value => health.Value = value);
[Test]
public void TestBeatmapState()
{
float progress = 0;
AddStep("set unavailable", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.NotDownloaded()));
AddStep("set downloading", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress = 0)));
AddUntilStep("increment progress", () =>
{
progress += RNG.NextSingle(0.1f);
MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(progress));
return progress >= 1;
});
AddStep("set to importing", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.Importing()));
AddStep("set to available", () => MultiplayerClient.ChangeBeatmapAvailability(BeatmapAvailability.LocallyAvailable()));
}
}
}
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
@@ -17,7 +18,10 @@ using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Users.Drawables;
using osuTK;
using osuTK.Graphics;
@@ -42,6 +46,13 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components
private BufferedContainer grayScaleContainer = null!;
private OsuSpriteText beatmapState = null!;
private BeatmapAvailability availability = BeatmapAvailability.Unknown();
[Resolved]
private MultiplayerClient client { get; set; } = null!;
[Resolved]
private RankedPlayCornerPiece? cornerPiece { get; set; }
@@ -61,6 +72,10 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components
? -OsuGame.SHEAR
: OsuGame.SHEAR;
var beatmapStateAnchor = (contentAnchor & Anchor.x0) != 0
? Anchor.CentreLeft
: Anchor.CentreRight;
InternalChildren =
[
new CircularContainer
@@ -103,15 +118,33 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components
Anchor = contentAnchor,
Origin = contentAnchor,
},
new OsuSpriteText
new FillFlowContainer
{
Name = "Username",
Text = user.Username,
Name = "Username/beatmap state container",
AutoSizeAxes = Axes.Both,
Anchor = contentAnchor,
Origin = contentAnchor,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding { Horizontal = 4, Vertical = 6 },
Font = OsuFont.GetFont(size: 24, weight: FontWeight.SemiBold),
UseFullGlyphHeight = false,
Spacing = new Vector2(5, 0),
Children =
[
new OsuSpriteText
{
Name = "Username",
Text = user.Username,
Anchor = contentAnchor,
Origin = contentAnchor,
Font = OsuFont.GetFont(size: 24, weight: FontWeight.SemiBold),
UseFullGlyphHeight = false,
},
beatmapState = new OsuSpriteText
{
Anchor = beatmapStateAnchor,
Origin = beatmapStateAnchor,
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
},
],
},
]
}
@@ -129,6 +162,46 @@ namespace osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay.Components
grayScaleContainer.GrayscaleTo(e.NewValue <= 0 ? 1 : 0, 300);
cornerPiece?.OnHealthChanged(e.NewValue);
});
client.RoomUpdated += onRoomUpdated;
}
private void onRoomUpdated()
{
var user = client.Room?.Users.SingleOrDefault(u => u.UserID == userId);
if (user == null || availability == user.BeatmapAvailability)
return;
availability = user.BeatmapAvailability;
if (availability.State is DownloadState.NotDownloaded or DownloadState.Downloading or DownloadState.Importing)
beatmapState.FadeIn(50);
else
beatmapState.FadeOut(50);
switch (availability.State)
{
case DownloadState.NotDownloaded:
beatmapState.Text = "Missing Beatmap";
break;
case DownloadState.Downloading:
double progress = Math.Clamp(availability.DownloadProgress ?? 0, 0, 1);
beatmapState.Text = $"Downloading... ({progress:P0})";
break;
case DownloadState.Importing:
beatmapState.Text = "Importing...";
break;
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
client.RoomUpdated -= onRoomUpdated;
}
public partial class HealthBar : CompositeDrawable