1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-19 10:40:33 +08:00

Add simple multiplayer skip overlay

This commit is contained in:
Dan Balasescu
2025-10-31 21:39:33 +09:00
Unverified
parent d0ce74063d
commit b20a41c1e8
7 changed files with 213 additions and 16 deletions
@@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Drawable OverlayContent => InternalChild;
public Drawable FadingContent => (OverlayContent as Container)?.Child;
public new Drawable FadingContent => (OverlayContent as Container)?.Child;
}
}
}
@@ -0,0 +1,73 @@
// 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 NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerSkipOverlay : MultiplayerTestScene
{
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
WaitForJoined();
AddStep("add skip overlay", () =>
{
GameplayClockContainer gameplayClockContainer;
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new MultiplayerSkipOverlay(120000)
},
};
gameplayClockContainer.Start();
});
AddStep("set playing state", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Playing));
}
[Test]
public void TestSkip()
{
for (int i = 0; i < 4; i++)
{
int i2 = i;
AddStep($"join user {i2}", () =>
{
MultiplayerClient.AddUser(new APIUser
{
Id = i2,
Username = $"User {i2}"
});
MultiplayerClient.ChangeUserState(i2, MultiplayerUserState.Playing);
});
}
AddStep("local user votes", () => MultiplayerClient.VoteToSkip().WaitSafely());
for (int i = 0; i < 4; i++)
{
int i2 = i;
AddStep($"user {i2} votes", () => MultiplayerClient.UserVoteToSkip(i2).WaitSafely());
}
}
}
}
@@ -148,6 +148,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Debug.Assert(client.Room != null);
}
protected override SkipOverlay CreateSkipOverlay(double startTime) => new MultiplayerSkipOverlay(startTime);
protected override void StartGameplay()
{
// We can enter this screen one of two ways:
@@ -0,0 +1,114 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Play;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public partial class MultiplayerSkipOverlay : SkipOverlay
{
[Resolved]
private MultiplayerClient client { get; set; } = null!;
private Drawable votedIcon = null!;
private OsuSpriteText countText = null!;
public MultiplayerSkipOverlay(double startTime)
: base(startTime)
{
}
[BackgroundDependencyLoader]
private void load()
{
FadingContent.AddRange(
[
votedIcon = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Position = new Vector2(50, 0),
Size = new Vector2(20),
Alpha = 0,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Green
},
new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.5f),
Icon = FontAwesome.Solid.Check
}
}
},
countText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.X,
Position = new Vector2(0.75f, 0),
Font = OsuFont.Default.With(size: 36, weight: FontWeight.Bold)
}
]);
}
protected override void LoadComplete()
{
base.LoadComplete();
client.UserStateChanged += onUserStateChanged;
client.UserVotedToSkip += onUserVotedToSkip;
updateText();
}
private void onUserStateChanged(MultiplayerRoomUser user, MultiplayerUserState state)
{
Schedule(updateText);
}
private void onUserVotedToSkip(int userId) => Schedule(() =>
{
updateText();
countText.ScaleTo(1.5f).ScaleTo(1, 200, Easing.OutSine);
if (userId == client.LocalUser?.UserID)
{
votedIcon.ScaleTo(1.5f).ScaleTo(1, 200, Easing.OutSine);
votedIcon.FadeInFromZero(100);
}
});
private void updateText()
{
if (client.Room == null)
return;
int countTotal = client.Room.Users.Count(u => u.State == MultiplayerUserState.Playing);
int countSkipped = client.Room.Users.Count(u => u.State == MultiplayerUserState.Playing && u.VotedToSkip);
int countRequired = countTotal / 2 + 1;
countText.Text = $"{Math.Min(countRequired, countSkipped)} / {countRequired}";
}
}
}
+8 -6
View File
@@ -154,7 +154,7 @@ namespace osu.Game.Screens.Play
private BreakTracker breakTracker;
private SkipOverlay skipIntroOverlay;
protected SkipOverlay SkipIntroOverlay { get; private set; }
private SkipOverlay skipOutroOverlay;
protected ScoreProcessor ScoreProcessor { get; private set; }
@@ -500,10 +500,10 @@ namespace osu.Game.Screens.Play
},
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
skipIntroOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
SkipIntroOverlay = CreateSkipOverlay(DrawableRuleset.GameplayStartTime).With(o =>
{
RequestSkip = RequestIntroSkip
},
o.RequestSkip = RequestIntroSkip;
}),
skipOutroOverlay = new SkipOverlay(GameplayState.Storyboard.LatestEventTime ?? 0)
{
RequestSkip = () => progressToResults(false),
@@ -522,13 +522,15 @@ namespace osu.Game.Screens.Play
if (!Configuration.AllowSkipping || !DrawableRuleset.AllowGameplayOverlays)
{
skipIntroOverlay.Expire();
SkipIntroOverlay.Expire();
skipOutroOverlay.Expire();
}
return container;
}
protected virtual SkipOverlay CreateSkipOverlay(double startTime) => new SkipOverlay(startTime);
private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime)
{
updateGameplayState();
@@ -1158,7 +1160,7 @@ namespace osu.Game.Screens.Play
GameplayClockContainer.Reset(startClock: true);
if (Configuration.AutomaticallySkipIntro)
skipIntroOverlay.SkipWhenReady();
SkipIntroOverlay.SkipWhenReady();
}
public override void OnSuspending(ScreenTransitionEvent e)
+9 -8
View File
@@ -38,20 +38,21 @@ namespace osu.Game.Screens.Play
private readonly double startTime;
public Action RequestSkip;
protected FadeContainer FadingContent { get; private set; }
private Button button;
private ButtonContainer buttonContainer;
private Circle remainingTimeBox;
private FadeContainer fadeContainer;
private double displayTime;
private bool isClickable;
private bool skipQueued;
[Resolved]
private IGameplayClock gameplayClock { get; set; }
internal bool IsButtonVisible => fadeContainer.State == Visibility.Visible && buttonContainer.State.Value == Visibility.Visible;
internal bool IsButtonVisible => FadingContent.State == Visibility.Visible && buttonContainer.State.Value == Visibility.Visible;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
/// <summary>
@@ -77,7 +78,7 @@ namespace osu.Game.Screens.Play
InternalChild = buttonContainer = new ButtonContainer
{
RelativeSizeAxes = Axes.Both,
Child = fadeContainer = new FadeContainer
Child = FadingContent = new FadeContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
@@ -107,13 +108,13 @@ namespace osu.Game.Screens.Play
public override void Hide()
{
base.Hide();
fadeContainer.Hide();
FadingContent.Hide();
}
public override void Show()
{
base.Show();
fadeContainer.TriggerShow();
FadingContent.TriggerShow();
}
protected override void LoadComplete()
@@ -136,7 +137,7 @@ namespace osu.Game.Screens.Play
RequestSkip?.Invoke();
};
fadeContainer.TriggerShow();
FadingContent.TriggerShow();
}
/// <summary>
@@ -183,7 +184,7 @@ namespace osu.Game.Screens.Play
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (isClickable && !e.HasAnyButtonPressed)
fadeContainer.TriggerShow();
FadingContent.TriggerShow();
return base.OnMouseMove(e);
}
@@ -563,7 +563,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
public override Task VoteToSkip()
{
return Task.CompletedTask;
return UserVoteToSkip(api.LocalUser.Value.OnlineID);
}
public async Task UserVoteToSkip(int userId)
{
await ((IMultiplayerClient)this).UserVotedToSkip(userId);
}
protected override Task<MultiplayerRoom> CreateRoomInternal(MultiplayerRoom room)