osu
osu copied to clipboard
Add delayed resume for taiko/catch/mania
Resolves https://github.com/ppy/osu/issues/10202 Supersedes / closes https://github.com/ppy/osu/pull/26992
The lack of any visual cue here is bothering me. It's almost like a second pause, I find myself struggling to anticipate the moment where gameplay resumes.
Maybe we could do something super simple like this even at least? (very rough)
diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs
index ef39c8eb76..5dfbb681c9 100644
--- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs
+++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs
@@ -1,6 +1,9 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Threading;
@@ -14,10 +17,24 @@ public partial class DelayedResumeOverlay : ResumeOverlay
protected override LocalisableString Message => string.Empty;
private ScheduledDelegate? scheduledResume;
+ private Box background = null!;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Colour4.Black,
+ Alpha = 0.75f,
+ });
+ }
protected override void PopIn()
{
- base.PopIn();
+ this.FadeIn();
+ background.FadeIn()
+ .Then().FadeOut(800, Easing.OutSine);
scheduledResume?.Cancel();
scheduledResume = Scheduler.AddDelayed(Resume, 800);
@@ -25,7 +42,8 @@ protected override void PopIn()
protected override void PopOut()
{
- base.PopOut();
+ this.FadeOut();
+
scheduledResume?.Cancel();
}
}
I didn't really like the fading overlay because it doesn't work well with the classic taiko skin. I tried to add a countdown instead:
https://drive.google.com/file/d/1wAnOjFAirLGd7cn2jULz0A7sl0jD2q0q/view?usp=sharing
This needs sound effects added of any kind.
Also I dunno, it still feels super out of place and awkward.
Also I dunno, it still feels super out of place and awkward.
Is that in relation to the design or what? This originally didn't have any design which matches stable. I imagine a world in which every ruleset has a resume overlay that matches gameplay, but that increases the scope of this PR a thousand-fold.
It was intended as an early thing that could be agreed to without much discussion...
I've added what I felt would make it good and got this:
https://github.com/ppy/osu/assets/22781491/416aa1ca-8883-4b54-bd99-79e02f79c943
Basically made the countdown appear specifically on the center of the screen, and added a dim to indicate that the game is still paused and there's a countdown.
patch
diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
index adc7bd97ff..2401b044c4 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
@@ -28,6 +28,22 @@ public partial class OsuResumeOverlay : ResumeOverlay
protected override LocalisableString Message => "Click the orange cursor to resume";
+ private readonly Container content;
+
+ protected override Container<Drawable> Content => content;
+
+ public OsuResumeOverlay()
+ {
+ InternalChild = new OsuPlayfieldAdjustmentContainer
+ {
+ AlignWithStoryboard = true,
+ Child = content = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ };
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -40,7 +56,7 @@ private void load()
protected override void PopIn()
{
// Can't display if the cursor is outside the window.
- if (GameplayCursor.LastFrameState == Visibility.Hidden || !Contains(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))
+ if (GameplayCursor.LastFrameState == Visibility.Hidden || !Content.Contains(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))
{
Resume();
return;
@@ -49,7 +65,7 @@ protected override void PopIn()
base.PopIn();
GameplayCursor.ActiveCursor.Hide();
- cursorScaleContainer.Position = ToLocalSpace(GameplayCursor.ActiveCursor.ScreenSpaceDrawQuad.Centre);
+ cursorScaleContainer.Position = GameplayCursor.ActiveCursor.Position;
clickToResumeCursor.Appear();
if (localCursorContainer == null)
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index a422761800..1f474af10f 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -207,8 +207,7 @@ private void load(CancellationToken? cancellationToken)
if ((ResumeOverlay = CreateResumeOverlay()) != null)
{
AddInternal(CreateInputManager()
- .WithChild(CreatePlayfieldAdjustmentContainer()
- .WithChild(ResumeOverlay)));
+ .WithChild(ResumeOverlay));
}
applyRulesetMods(Mods, config);
diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs
index fd1ce5d829..392c636cc2 100644
--- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs
+++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs
@@ -3,6 +3,7 @@
using System;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -14,6 +15,8 @@
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK;
+using osuTK.Graphics;
+using Box = osu.Framework.Graphics.Shapes.Box;
namespace osu.Game.Screens.Play
{
@@ -39,6 +42,7 @@ public partial class DelayedResumeOverlay : ResumeOverlay
private double countdownStartTime;
private bool countdownComplete;
+ private Box dim = null!;
private Drawable outerContent = null!;
private Container innerContent = null!;
@@ -56,6 +60,12 @@ public DelayedResumeOverlay()
[BackgroundDependencyLoader]
private void load()
{
+ Add(dim = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black.Opacity(0.75f),
+ });
+
Add(outerContent = new Circle
{
Anchor = Anchor.Centre,
@@ -109,6 +119,8 @@ protected override void PopIn()
{
this.FadeIn();
+ dim.FadeIn();
+
// The transition effects.
outerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 200, Easing.OutQuint);
innerContent.FadeIn().ScaleTo(Vector2.Zero).Then().ScaleTo(Vector2.One, 400, Easing.OutElasticHalf);
@@ -143,9 +155,13 @@ protected override void PopOut()
{
countdownProgress.ScaleTo(2f, 300, Easing.OutQuint);
countdownProgress.FadeOut(300, Easing.OutQuint);
+ dim.FadeOut(300, Easing.OutQuint);
}
else
+ {
countdownProgress.FadeOut();
+ dim.FadeOut(GameplayMenuOverlay.TRANSITION_DURATION, Easing.InQuint);
+ }
scheduledResume?.Cancel();
}
diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
index da239d585e..e87c04363d 100644
--- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs
+++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play
{
public abstract partial class GameplayMenuOverlay : OverlayContainer, IKeyBindingHandler<GlobalAction>
{
- protected const int TRANSITION_DURATION = 200;
+ public const int TRANSITION_DURATION = 200;
private const int button_height = 70;
private const float background_alpha = 0.75f;
Dim is too harsh, the idea is to prepare for gameplay so you need to be able to see it.
@peppy asked me to whip some sound design for this, so here's something I quickly put together:
https://github.com/ppy/osu/assets/272140/814952ea-5d68-425d-8ac4-88487bb608db
Feel free to adjust to taste or whatever. PR for sample: https://github.com/ppy/osu-resources/pull/314
diff
diff --git a/osu.Game/Screens/Play/DelayedResumeOverlay.cs b/osu.Game/Screens/Play/DelayedResumeOverlay.cs
index 8bb3ae8182..9de6f94d59 100644
--- a/osu.Game/Screens/Play/DelayedResumeOverlay.cs
+++ b/osu.Game/Screens/Play/DelayedResumeOverlay.cs
@@ -3,6 +3,8 @@
using System;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -47,6 +49,8 @@ public partial class DelayedResumeOverlay : ResumeOverlay
private SpriteText countdownText = null!;
private CircularProgress countdownProgress = null!;
+ private Sample sampleCountdown = null!;
+
public DelayedResumeOverlay()
{
Anchor = Anchor.Centre;
@@ -54,7 +58,7 @@ public DelayedResumeOverlay()
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(AudioManager audio)
{
Add(outerContent = new Circle
{
@@ -103,6 +107,8 @@ private void load()
}
}
});
+
+ sampleCountdown = audio.Samples.Get(@"Gameplay/resume-countdown");
}
protected override void PopIn()
@@ -164,13 +170,20 @@ private void updateCountdown()
countdownProgress.Progress = amountTimePassed;
countdownProgress.InnerRadius = progress_stroke_width / progress_size / countdownProgress.Scale.X;
- if (countdownCount != newCount && newCount > 0)
+ if (countdownCount != newCount)
{
- countdownText.Text = Math.Max(1, newCount).ToString();
- countdownText.ScaleTo(0.25f).Then().ScaleTo(1, 200, Easing.OutQuint);
- outerContent.Delay(25).Then().ScaleTo(1.05f, 100).Then().ScaleTo(1f, 200, Easing.Out);
+ if (newCount > 0)
+ {
+ countdownText.Text = Math.Max(1, newCount).ToString();
+ countdownText.ScaleTo(0.25f).Then().ScaleTo(1, 200, Easing.OutQuint);
+ outerContent.Delay(25).Then().ScaleTo(1.05f, 100).Then().ScaleTo(1f, 200, Easing.Out);
+
+ countdownBackground.FlashColour(colourProvider.Background3, 400, Easing.Out);
+ }
- countdownBackground.FlashColour(colourProvider.Background3, 400, Easing.Out);
+ var chan = sampleCountdown.GetChannel();
+ chan.Frequency.Value = newCount == 0 ? 0.5f : 1;
+ chan.Play();
}
countdownCount = newCount;