1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-30 20:33:01 +08:00

Add replay / spectator mode scrolling text back (#36911)

As mentioned in https://github.com/ppy/osu/discussions/36883.

This has caught me off-guard a few times.

Was a quick one to make this work like it does on stable. It doesn't fit
as well as stable because we have a lot of elements at the top of the
screen, but I think it's better than nothing, as it lets you know you're
in a replay quick obviously.

I don't think we can easily localise strings with formatting in them
yet. Maybe using a `MarkdownContainer` or something?
This commit is contained in:
Dean Herbert
2026-03-11 19:08:26 +09:00
committed by GitHub
Unverified
parent 4c0657f4ca
commit d0d5d97cfe
8 changed files with 150 additions and 50 deletions
@@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Ranking
AddAssert("mapped by text not present", () =>
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by")));
AddAssert("play time displayed", () => this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any());
AddAssert("play time displayed", () => this.ChildrenOfType<PlayedOnText>().Any());
}
[Test]
@@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Ranking
showPanel(score);
});
AddAssert("play time not displayed", () => !this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any());
AddAssert("play time not displayed", () => !this.ChildrenOfType<PlayedOnText>().Any());
}
[Test]
+2
View File
@@ -1,6 +1,7 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
@@ -20,6 +21,7 @@ namespace osu.Game.Rulesets.Mods
Replay = replayData.Replay,
ScoreInfo =
{
Date = DateTimeOffset.Now,
User = new APIUser
{
Id = replayData.User.OnlineID,
+8 -2
View File
@@ -311,7 +311,7 @@ namespace osu.Game.Screens.Play
{
// underlay and gameplay should have access to the skinning sources.
createUnderlayComponents(Beatmap.Value),
createGameplayComponents(Beatmap.Value)
createGameplayComponents()
}
},
FailOverlay = new FailOverlay
@@ -426,6 +426,11 @@ namespace osu.Game.Screens.Play
IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
}
/// <summary>
/// Implement to add any components which should exist above gameplay but below the HUD.
/// </summary>
protected virtual Drawable CreateOverlayComponents() => Empty();
protected virtual GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart);
private Drawable createUnderlayComponents(WorkingBeatmap working)
@@ -451,7 +456,7 @@ namespace osu.Game.Screens.Play
return container;
}
private Drawable createGameplayComponents(IWorkingBeatmap working) => new ScalingContainer(ScalingMode.Gameplay)
private Drawable createGameplayComponents() => new ScalingContainer(ScalingMode.Gameplay)
{
Children = new Drawable[]
{
@@ -474,6 +479,7 @@ namespace osu.Game.Screens.Play
Children = new[]
{
DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
CreateOverlayComponents(),
HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods, Configuration)
{
HoldToQuit =
+25
View File
@@ -12,12 +12,15 @@ using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play.Leaderboards;
using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Ranking.Expanded;
using osu.Game.Skinning;
using osu.Game.Users;
@@ -97,6 +100,7 @@ namespace osu.Game.Screens.Play
playbackSettings.UserPlaybackRate.BindTo(master.UserPlaybackRate);
HUDOverlay.PlayerSettingsOverlay.AddAtStart(playbackSettings);
AddInternal(new RulesetSkinProvidingContainer(GameplayState.Ruleset, GameplayState.Beatmap, Beatmap.Value.Skin)
{
Child = failIndicator = new ReplayFailIndicator(GameplayClockContainer)
@@ -113,6 +117,27 @@ namespace osu.Game.Screens.Play
});
}
protected override Drawable CreateOverlayComponents()
{
OsuTextFlowContainer message = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Style.Body) { AutoSizeAxes = Axes.Both };
message.AddText("Watching ");
message.AddText(Score.ScoreInfo.User.Username, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
message.AddText(" play ");
message.AddText(Beatmap.Value.BeatmapInfo.GetDisplayTitleRomanisable(), s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
message.AddText(" on ");
message.AddArbitraryDrawable(new PlayedOnText(Score.ScoreInfo.Date, false)
{
Font = OsuFont.Style.Body.With(weight: FontWeight.SemiBold),
});
return new ScrollingMessage(message)
{
Y = 100,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
};
}
protected override void PrepareReplay()
{
DrawableRuleset?.SetReplayScore(Score);
+44
View File
@@ -0,0 +1,44 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Screens.Play
{
public partial class ScrollingMessage : CompositeDrawable
{
private readonly Drawable messageContent;
public ScrollingMessage(Drawable messageContent)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = this.messageContent = messageContent;
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(2000, Easing.OutQuint);
resetMessagePosition();
}
protected override void Update()
{
base.Update();
if (messageContent.X + messageContent.DrawWidth > 0)
messageContent.X -= (float)Clock.ElapsedFrameTime * 0.05f;
else
resetMessagePosition();
}
private void resetMessagePosition()
{
messageContent.X = DrawWidth + 10;
}
}
}
+13 -7
View File
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
@@ -29,17 +29,23 @@ namespace osu.Game.Screens.Play
this.score = score;
}
[BackgroundDependencyLoader]
private void load()
protected override Drawable CreateOverlayComponents()
{
AddInternal(new OsuSpriteText
// TODO: This should be customised for `MultiplayerSpectatorPlayer` to be static and only show the player name.
// Or maybe we should completely redesign this to show the user avatar and other things if that happens.
OsuTextFlowContainer message = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Style.Body) { AutoSizeAxes = Axes.Both };
message.AddText("Watching ");
message.AddText(Score.ScoreInfo.User.Username, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
message.AddText(" play ");
message.AddText(Beatmap.Value.BeatmapInfo.GetDisplayTitleRomanisable(), s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
message.AddText(" live", s => s.Font = s.Font.With(weight: FontWeight.Bold));
return new ScrollingMessage(message)
{
Text = $"Watching {score.ScoreInfo.User.Username} playing live!",
Font = OsuFont.Default.With(size: 30),
Y = 100,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
});
};
}
protected override void LoadComplete()
@@ -1,19 +1,15 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -237,7 +233,7 @@ namespace osu.Game.Screens.Ranking.Expanded
});
if (score.Date != default)
AddInternal(new PlayedOnText(score.Date));
AddInternal(new PlayedOnText(score.Date, true));
}
protected override void LoadComplete()
@@ -268,40 +264,6 @@ namespace osu.Game.Screens.Ranking.Expanded
});
}
public partial class PlayedOnText : OsuSpriteText
{
private readonly DateTimeOffset time;
private readonly Bindable<bool> prefer24HourTime = new Bindable<bool>();
public PlayedOnText(DateTimeOffset time)
{
this.time = time;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager configManager)
{
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
}
protected override void LoadComplete()
{
base.LoadComplete();
prefer24HourTime.BindValueChanged(_ => updateDisplay(), true);
}
private void updateDisplay()
{
Text = LocalisableString.Format("Played on {0}",
time.ToLocalTime().ToLocalisableString(prefer24HourTime.Value ? @"d MMMM yyyy HH:mm" : @"d MMMM yyyy h:mm tt"));
}
}
internal partial class ClickableMetadata : OsuHoverContainer
{
[Resolved]
@@ -0,0 +1,55 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Screens.Ranking.Expanded
{
public partial class PlayedOnText : OsuSpriteText
{
private readonly DateTimeOffset time;
private readonly bool withPrefix;
private readonly Bindable<bool> prefer24HourTime = new Bindable<bool>();
public PlayedOnText(DateTimeOffset time, bool withPrefix)
{
this.time = time;
this.withPrefix = withPrefix;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager configManager)
{
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
}
protected override void LoadComplete()
{
base.LoadComplete();
prefer24HourTime.BindValueChanged(_ => updateDisplay(), true);
}
private void updateDisplay()
{
var timeText = time.ToLocalTime().ToLocalisableString(prefer24HourTime.Value ? @"d MMMM yyyy HH:mm" : @"d MMMM yyyy h:mm tt");
if (withPrefix)
Text = LocalisableString.Format("Played on {0}", timeText);
else
Text = timeText;
}
}
}