1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 20:07:25 +08:00

Merge pull request #5499 from frenzibyte/ingame-leaderboard-general-implementation

Add base implementation for in-game leaderboards
This commit is contained in:
Dean Herbert 2020-12-15 22:09:25 +09:00 committed by GitHub
commit d0bcab3c98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 282 additions and 0 deletions

View File

@ -0,0 +1,80 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Screens.Play.HUD;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
public class TestSceneGameplayLeaderboard : OsuTestScene
{
private readonly TestGameplayLeaderboard leaderboard;
private readonly BindableDouble playerScore = new BindableDouble();
public TestSceneGameplayLeaderboard()
{
Add(leaderboard = new TestGameplayLeaderboard
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(2),
RelativeSizeAxes = Axes.X,
});
}
[SetUpSteps]
public void SetUpSteps()
{
AddStep("reset leaderboard", () =>
{
leaderboard.Clear();
playerScore.Value = 1222333;
});
AddStep("add player user", () => leaderboard.AddPlayer(playerScore, new User { Username = "You" }));
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
}
[Test]
public void TestPlayerScore()
{
var player2Score = new BindableDouble(1234567);
var player3Score = new BindableDouble(1111111);
AddStep("add player 2", () => leaderboard.AddPlayer(player2Score, new User { Username = "Player 2" }));
AddStep("add player 3", () => leaderboard.AddPlayer(player3Score, new User { Username = "Player 3" }));
AddAssert("is player 2 position #1", () => leaderboard.CheckPositionByUsername("Player 2", 1));
AddAssert("is player position #2", () => leaderboard.CheckPositionByUsername("You", 2));
AddAssert("is player 3 position #3", () => leaderboard.CheckPositionByUsername("Player 3", 3));
AddStep("set score above player 3", () => player2Score.Value = playerScore.Value - 500);
AddAssert("is player position #1", () => leaderboard.CheckPositionByUsername("You", 1));
AddAssert("is player 2 position #2", () => leaderboard.CheckPositionByUsername("Player 2", 2));
AddAssert("is player 3 position #3", () => leaderboard.CheckPositionByUsername("Player 3", 3));
AddStep("set score below players", () => player2Score.Value = playerScore.Value - 123456);
AddAssert("is player position #1", () => leaderboard.CheckPositionByUsername("You", 1));
AddAssert("is player 3 position #2", () => leaderboard.CheckPositionByUsername("Player 3", 2));
AddAssert("is player 2 position #3", () => leaderboard.CheckPositionByUsername("Player 2", 3));
}
private class TestGameplayLeaderboard : GameplayLeaderboard
{
public bool CheckPositionByUsername(string username, int? expectedPosition)
{
var scoreItem = this.FirstOrDefault(i => i.User.Username == username);
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
}
}
}
}

View File

@ -0,0 +1,66 @@
// 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.Linq;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class GameplayLeaderboard : FillFlowContainer<GameplayLeaderboardScore>
{
public GameplayLeaderboard()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Vertical;
Spacing = new Vector2(2.5f);
LayoutDuration = 250;
LayoutEasing = Easing.OutQuint;
}
/// <summary>
/// Adds a player to the leaderboard.
/// </summary>
/// <param name="currentScore">The bindable current score of the player.</param>
/// <param name="user">The player.</param>
public void AddPlayer([NotNull] BindableDouble currentScore, [NotNull] User user)
{
var scoreItem = addScore(currentScore.Value, user);
currentScore.ValueChanged += s => scoreItem.TotalScore = s.NewValue;
}
private GameplayLeaderboardScore addScore(double totalScore, User user)
{
var scoreItem = new GameplayLeaderboardScore
{
User = user,
TotalScore = totalScore,
OnScoreChange = updateScores,
};
Add(scoreItem);
updateScores();
return scoreItem;
}
private void updateScores()
{
var orderedByScore = this.OrderByDescending(i => i.TotalScore).ToList();
for (int i = 0; i < Count; i++)
{
SetLayoutPosition(orderedByScore[i], i);
orderedByScore[i].ScorePosition = i + 1;
}
}
}
}

View File

@ -0,0 +1,136 @@
// 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 Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class GameplayLeaderboardScore : CompositeDrawable
{
private readonly OsuSpriteText positionText, positionSymbol, userString;
private readonly GlowingSpriteText scoreText;
public Action OnScoreChange;
private int? scorePosition;
public int? ScorePosition
{
get => scorePosition;
set
{
scorePosition = value;
if (scorePosition.HasValue)
positionText.Text = $"#{scorePosition.Value.ToMetric(decimals: scorePosition < 100000 ? 1 : 0)}";
positionText.FadeTo(scorePosition.HasValue ? 1 : 0);
positionSymbol.FadeTo(scorePosition.HasValue ? 1 : 0);
}
}
private double totalScore;
public double TotalScore
{
get => totalScore;
set
{
totalScore = value;
scoreText.Text = totalScore.ToString("N0");
OnScoreChange?.Invoke();
}
}
private User user;
public User User
{
get => user;
set
{
user = value;
userString.Text = user?.Username;
}
}
public GameplayLeaderboardScore()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new Container
{
Masking = true,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Right = 2.5f },
Spacing = new Vector2(2.5f),
Children = new[]
{
positionText = new OsuSpriteText
{
Alpha = 0,
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold),
},
positionSymbol = new OsuSpriteText
{
Alpha = 0,
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
Text = ">",
},
}
},
new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Left = 2.5f },
Spacing = new Vector2(2.5f),
Children = new Drawable[]
{
userString = new OsuSpriteText
{
Size = new Vector2(80, 16),
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
},
scoreText = new GlowingSpriteText
{
GlowColour = Color4Extensions.FromHex(@"83ccfa"),
Font = OsuFont.Numeric.With(size: 14),
}
}
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
positionText.Colour = colours.YellowLight;
positionSymbol.Colour = colours.Yellow;
}
}
}