1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 16:25:32 +08:00

Merge pull request #27498 from frenzibyte/argon-pp-counter

Add "Argon" performance points counter
This commit is contained in:
Dean Herbert 2024-03-08 11:16:10 +08:00 committed by GitHub
commit 6323189cfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 255 additions and 155 deletions

View File

@ -60,6 +60,8 @@ namespace osu.Game.Tests.Skins
"Archives/modified-argon-20231106.osk",
// Covers "Argon" accuracy/score/combo counters, and wedges
"Archives/modified-argon-20231108.osk",
// Covers "Argon" performance points counter
"Archives/modified-argon-20240305.osk",
};
/// <summary>

View File

@ -1,8 +1,8 @@
// 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.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Skinning;
@ -13,8 +13,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
[SetUp]
public void SetUp() => Schedule(() =>
[SetUpSteps]
public virtual void SetUpSteps()
{
AddStep("setup components", SetUpComponents);
}
public void SetUpComponents()
{
SetContents(skin =>
{
@ -28,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay
implementation.Origin = Anchor.Centre;
return implementation;
});
});
}
protected abstract Drawable CreateDefaultImplementation();
protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation();

View File

@ -3,102 +3,89 @@
#nullable disable
using System;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osuTK;
using osu.Game.Skinning.Triangles;
using osu.Game.Tests.Gameplay;
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestScenePerformancePointsCounter : OsuTestScene
public partial class TestScenePerformancePointsCounter : SkinnableHUDComponentTestScene
{
private DependencyProvidingContainer dependencyContainer;
[Cached(typeof(ScoreProcessor))]
private readonly ScoreProcessor scoreProcessor = new OsuScoreProcessor();
private GameplayState gameplayState;
private ScoreProcessor scoreProcessor;
[Cached]
private readonly GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
private int iteration;
private Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
private PerformancePointsCounter counter;
[SetUpSteps]
public void SetUpSteps() => AddStep("create components", () =>
protected override Drawable CreateDefaultImplementation() => new TrianglesPerformancePointsCounter();
protected override Drawable CreateArgonImplementation() => new ArgonPerformancePointsCounter();
protected override Drawable CreateLegacyImplementation() => Empty();
private Bindable<JudgementResult> lastJudgementResult => (Bindable<JudgementResult>)gameplayState.LastJudgementResult;
public override void SetUpSteps()
{
var ruleset = CreateRuleset();
Debug.Assert(ruleset != null);
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
.GetPlayableBeatmap(ruleset.RulesetInfo);
lastJudgementResult = new Bindable<JudgementResult>();
gameplayState = new GameplayState(beatmap, ruleset);
gameplayState.LastJudgementResult.BindTo(lastJudgementResult);
scoreProcessor = new ScoreProcessor(ruleset);
Child = dependencyContainer = new DependencyProvidingContainer
AddStep("reset", () =>
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(GameplayState), gameplayState),
(typeof(ScoreProcessor), scoreProcessor)
}
};
var ruleset = new OsuRuleset();
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
.GetPlayableBeatmap(ruleset.RulesetInfo);
iteration = 0;
});
iteration = 0;
scoreProcessor.ApplyBeatmap(beatmap);
lastJudgementResult.SetDefault();
});
protected override Ruleset CreateRuleset() => new OsuRuleset();
base.SetUpSteps();
}
private void createCounter() => AddStep("Create counter", () =>
[Test]
public void TestDisplay()
{
dependencyContainer.Child = counter = new PerformancePointsCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(5),
};
});
AddSliderStep("pp", 0, 2000, 0, v => this.ChildrenOfType<PerformancePointsCounter>().ForEach(c => c.Current.Value = v));
AddToggleStep("toggle validity", v => this.ChildrenOfType<PerformancePointsCounter>().ForEach(c => c.IsValid = v));
}
[Test]
public void TestBasicCounting()
{
int previousValue = 0;
createCounter();
AddAssert("counter displaying zero", () => counter.Current.Value == 0);
AddAssert("counter displaying zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value == 0));
AddRepeatStep("Add judgement", applyOneJudgement, 10);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
AddUntilStep("counter opaque", () => counter.Child.Alpha == 1);
AddUntilStep("counter non-zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value > 0));
AddUntilStep("counter valid", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.IsValid));
AddStep("Revert judgement", () =>
{
previousValue = counter.Current.Value;
previousValue = this.ChildrenOfType<PerformancePointsCounter>().First().Current.Value;
scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement()));
});
AddUntilStep("counter decreased", () => counter.Current.Value < previousValue);
AddUntilStep("counter decreased", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value < previousValue));
AddStep("Add judgement", applyOneJudgement);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
AddUntilStep("counter non-zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value > 0));
}
[Test]
@ -106,10 +93,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddRepeatStep("Add judgement", applyOneJudgement, 10);
createCounter();
AddStep("recreate counter", SetUpComponents);
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
AddUntilStep("counter opaque", () => counter.Child.Alpha == 1);
AddUntilStep("counter non-zero", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.Current.Value > 0));
AddUntilStep("counter valid", () => this.ChildrenOfType<PerformancePointsCounter>().All(c => c.IsValid));
}
private void applyOneJudgement()

View File

@ -4,7 +4,6 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@ -21,10 +20,11 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter();
[SetUpSteps]
public void SetUpSteps()
public override void SetUpSteps()
{
AddStep("Set initial accuracy", () => scoreProcessor.Accuracy.Value = 1);
base.SetUpSteps();
}
[Test]

View File

@ -4,7 +4,6 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
@ -25,14 +24,15 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };
[SetUpSteps]
public void SetUpSteps()
public override void SetUpSteps()
{
AddStep(@"Reset all", delegate
{
healthProcessor.Health.Value = 1;
healthProcessor.Failed += () => false; // health won't be updated if the processor gets into a "fail" state.
});
base.SetUpSteps();
}
protected override void Update()

View File

@ -19,7 +19,7 @@ namespace osu.Game.Screens.Play.HUD
{
protected override double RollingDuration => 250;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,

View File

@ -23,7 +23,7 @@ namespace osu.Game.Screens.Play.HUD
protected override double RollingDuration => 250;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,

View File

@ -0,0 +1,81 @@
// 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.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Localisation.SkinComponents;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonPerformancePointsCounter : PerformancePointsCounter, ISerialisableDrawable
{
private ArgonCounterTextComponent text = null!;
protected override double RollingDuration => 250;
private const float alpha_when_invalid = 0.3f;
[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};
[SettingSource(typeof(SkinnableComponentStrings), nameof(SkinnableComponentStrings.ShowLabel), nameof(SkinnableComponentStrings.ShowLabelDescription))]
public Bindable<bool> ShowLabel { get; } = new BindableBool(true);
public override bool IsValid
{
get => base.IsValid;
set
{
if (value == IsValid)
return;
base.IsValid = value;
text.FadeTo(value ? 1 : alpha_when_invalid, 1000, Easing.OutQuint);
}
}
public override int DisplayedCount
{
get => base.DisplayedCount;
set
{
base.DisplayedCount = value;
updateWireframe();
}
}
private void updateWireframe()
{
int digitsRequiredForDisplayCount = Math.Max(3, getDigitsRequiredForDisplayCount());
if (digitsRequiredForDisplayCount != text.WireframeTemplate.Length)
text.WireframeTemplate = new string('#', digitsRequiredForDisplayCount);
}
private int getDigitsRequiredForDisplayCount()
{
int digitsRequired = 1;
long c = DisplayedCount;
while ((c /= 10) > 0)
digitsRequired++;
return digitsRequired;
}
protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopRight, BeatmapsetsStrings.ShowScoreboardHeaderspp.ToUpper())
{
WireframeOpacity = { BindTarget = WireframeOpacity },
ShowLabel = { BindTarget = ShowLabel },
};
}
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.HUD
protected override double RollingDuration => 250;
[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
[SettingSource("Wireframe opacity", "Controls the opacity of the wireframes behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,

View File

@ -13,16 +13,9 @@ using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Judgements;
@ -31,20 +24,13 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public partial class PerformancePointsCounter : RollingCounter<int>, ISerialisableDrawable
public abstract partial class PerformancePointsCounter : RollingCounter<int>
{
public bool UsesFixedAnchor { get; set; }
protected override bool IsRollingProportional => true;
protected override double RollingDuration => 500;
private const float alpha_when_invalid = 0.3f;
[Resolved]
private ScoreProcessor scoreProcessor { get; set; }
@ -60,18 +46,11 @@ namespace osu.Game.Screens.Play.HUD
private PerformanceCalculator performanceCalculator;
private ScoreInfo scoreInfo;
public PerformancePointsCounter()
{
Current.Value = DisplayedCount = 0;
}
private Mod[] clonedMods;
[BackgroundDependencyLoader]
private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache)
private void load(BeatmapDifficultyCache difficultyCache)
{
Colour = colours.BlueLighter;
if (gameplayState != null)
{
performanceCalculator = gameplayState.Ruleset.CreatePerformanceCalculator();
@ -107,19 +86,7 @@ namespace osu.Game.Screens.Play.HUD
onJudgementChanged(gameplayState.LastJudgementResult.Value);
}
private bool isValid;
protected bool IsValid
{
set
{
if (value == isValid)
return;
isValid = value;
DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint);
}
}
public virtual bool IsValid { get; set; }
private void onJudgementChanged(JudgementResult judgement)
{
@ -151,13 +118,6 @@ namespace osu.Game.Screens.Play.HUD
return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes;
}
protected override LocalisableString FormatCount(int count) => count.ToString(@"D");
protected override IHasText CreateText() => new TextComponent
{
Alpha = alpha_when_invalid
};
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
@ -171,45 +131,6 @@ namespace osu.Game.Screens.Play.HUD
loadCancellationSource?.Cancel();
}
private partial class TextComponent : CompositeDrawable, IHasText
{
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
}
private readonly OsuSpriteText text;
public TextComponent()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 16, fixedWidth: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = BeatmapsetsStrings.ShowScoreboardHeaderspp,
Font = OsuFont.Numeric.With(size: 8),
Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
}
}
};
}
}
// TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap.
private class GameplayWorkingBeatmap : WorkingBeatmap
{

View File

@ -118,6 +118,7 @@ namespace osu.Game.Skinning
var wedgePieces = container.OfType<ArgonWedgePiece>().ToArray();
var score = container.OfType<ArgonScoreCounter>().FirstOrDefault();
var accuracy = container.OfType<ArgonAccuracyCounter>().FirstOrDefault();
var performancePoints = container.OfType<ArgonPerformancePointsCounter>().FirstOrDefault();
var combo = container.OfType<ArgonComboCounter>().FirstOrDefault();
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
var keyCounter = container.OfType<ArgonKeyCounterDisplay>().FirstOrDefault();
@ -159,6 +160,13 @@ namespace osu.Game.Skinning
accuracy.Origin = Anchor.TopRight;
}
if (performancePoints != null && accuracy != null)
{
performancePoints.Position = new Vector2(accuracy.X, accuracy.Y + accuracy.DrawHeight + 10);
performancePoints.Anchor = Anchor.TopRight;
performancePoints.Origin = Anchor.TopRight;
}
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
if (hitError != null)
@ -224,6 +232,10 @@ namespace osu.Game.Skinning
CornerRadius = { Value = 0.5f }
},
new ArgonAccuracyCounter(),
new ArgonPerformancePointsCounter
{
Scale = new Vector2(0.8f),
},
new ArgonComboCounter
{
Scale = new Vector2(1.3f)

View File

@ -133,6 +133,11 @@ namespace osu.Game.Skinning
SkinLayoutInfo? layoutInfo = null;
// handle namespace changes...
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.PerformancePointsCounter", @"osu.Game.Skinning.Triangles.TrianglesPerformancePointsCounter");
try
{
// First attempt to deserialise using the new SkinLayoutInfo format
@ -150,10 +155,6 @@ namespace osu.Game.Skinning
// If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
if (layoutInfo == null)
{
// handle namespace changes...
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SerialisedDrawableInfo>>(jsonContent);
if (deserializedContent == null)

View File

@ -0,0 +1,90 @@
// 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Screens.Play.HUD;
using osuTK;
namespace osu.Game.Skinning.Triangles
{
public partial class TrianglesPerformancePointsCounter : PerformancePointsCounter, ISerialisableDrawable
{
protected override bool IsRollingProportional => true;
protected override double RollingDuration => 500;
private const float alpha_when_invalid = 0.3f;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.BlueLighter;
}
public override bool IsValid
{
get => base.IsValid;
set
{
if (value == IsValid)
return;
base.IsValid = value;
DrawableCount.FadeTo(value ? 1 : alpha_when_invalid, 1000, Easing.OutQuint);
}
}
protected override LocalisableString FormatCount(int count) => count.ToString(@"D");
protected override IHasText CreateText() => new TextComponent
{
Alpha = alpha_when_invalid
};
private partial class TextComponent : CompositeDrawable, IHasText
{
public LocalisableString Text
{
get => text.Text;
set => text.Text = value;
}
private readonly OsuSpriteText text;
public TextComponent()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 16, fixedWidth: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = BeatmapsetsStrings.ShowScoreboardHeaderspp,
Font = OsuFont.Numeric.With(size: 8),
Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
}
}
};
}
}
}
}

View File

@ -14,6 +14,7 @@ using osu.Game.Extensions;
using osu.Game.IO;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning.Triangles;
using osuTK;
using osuTK.Graphics;
@ -167,7 +168,7 @@ namespace osu.Game.Skinning
new DefaultKeyCounterDisplay(),
new BarHitErrorMeter(),
new BarHitErrorMeter(),
new PerformancePointsCounter()
new TrianglesPerformancePointsCounter()
}
};