Skip to content

Commit

Permalink
Merge pull request #25226 from frenzibyte/gameplay-hud-redesign/counters
Browse files Browse the repository at this point in the history
  • Loading branch information
peppy authored Nov 12, 2023
2 parents ccfdf1f + 3e8c89e commit b995c96
Show file tree
Hide file tree
Showing 16 changed files with 555 additions and 54 deletions.
Binary file not shown.
2 changes: 2 additions & 0 deletions osu.Game.Tests/Skins/SkinDeserialisationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public class SkinDeserialisationTest
"Archives/modified-argon-pro-20231001.osk",
// Covers player name text component.
"Archives/modified-argon-20231106.osk",
// Covers "Argon" accuracy/score/combo counters, and wedges
"Archives/modified-argon-20231108.osk",
};

/// <summary>
Expand Down
5 changes: 2 additions & 3 deletions osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public void TestDragSelection()

AddStep("Begin drag top left", () =>
{
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4));
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4, box1.ScreenSpaceDrawQuad.Height / 8));
InputManager.PressButton(MouseButton.Left);
});

Expand Down Expand Up @@ -147,8 +147,7 @@ public void TestCyclicSelection()
{
AddStep("Add big black box", () =>
{
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
InputManager.Click(MouseButton.Left);
skinEditor.ChildrenOfType<SkinComponentToolbox.ToolboxComponentButton>().First(b => b.ChildrenOfType<BigBlackBox>().FirstOrDefault() != null).TriggerClick();
});

AddStep("store box", () =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public partial class TestSceneSkinnableAccuracyCounter : SkinnableHUDComponentTe
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());

protected override Drawable CreateArgonImplementation() => new ArgonAccuracyCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultAccuracyCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyAccuracyCounter();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public partial class TestSceneSkinnableComboCounter : SkinnableHUDComponentTestS
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());

protected override Drawable CreateArgonImplementation() => new ArgonComboCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultComboCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyComboCounter();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class TestSceneSkinnableHealthDisplay : SkinnableHUDComponentTest
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);

protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f) };
protected override Drawable CreateArgonImplementation() => new ArgonHealthDisplay { Scale = new Vector2(0.6f), Width = 1f };
protected override Drawable CreateDefaultImplementation() => new DefaultHealthDisplay { Scale = new Vector2(0.6f) };
protected override Drawable CreateLegacyImplementation() => new LegacyHealthDisplay { Scale = new Vector2(0.6f) };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public partial class TestSceneSkinnableScoreCounter : SkinnableHUDComponentTestS
[Cached(typeof(ScoreProcessor))]
private ScoreProcessor scoreProcessor = TestGameplayState.Create(new OsuRuleset()).ScoreProcessor;

protected override Drawable CreateArgonImplementation() => new ArgonScoreCounter();
protected override Drawable CreateDefaultImplementation() => new DefaultScoreCounter();
protected override Drawable CreateLegacyImplementation() => new LegacyScoreCounter();

Expand Down
90 changes: 90 additions & 0 deletions osu.Game/Screens/Play/HUD/ArgonAccuracyCounter.cs
Original file line number Diff line number Diff line change
@@ -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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Skinning;
using osuTK;

namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonAccuracyCounter : GameplayAccuracyCounter, ISerialisableDrawable
{
protected override double RollingDuration => 500;
protected override Easing RollingEasing => Easing.OutQuint;

[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};

public bool UsesFixedAnchor { get; set; }

protected override IHasText CreateText() => new ArgonAccuracyTextComponent
{
WireframeOpacity = { BindTarget = WireframeOpacity },
};

private partial class ArgonAccuracyTextComponent : CompositeDrawable, IHasText
{
private readonly ArgonCounterTextComponent wholePart;
private readonly ArgonCounterTextComponent fractionPart;

public IBindable<float> WireframeOpacity { get; } = new BindableFloat();

public LocalisableString Text
{
get => wholePart.Text;
set
{
string[] split = value.ToString().Replace("%", string.Empty).Split(".");

wholePart.Text = split[0];
fractionPart.Text = "." + split[1];
}
}

public ArgonAccuracyTextComponent()
{
AutoSizeAxes = Axes.Both;

InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.Both,
Child = wholePart = new ArgonCounterTextComponent(Anchor.TopRight, "ACCURACY")
{
RequiredDisplayDigits = { Value = 3 },
WireframeOpacity = { BindTarget = WireframeOpacity }
}
},
fractionPart = new ArgonCounterTextComponent(Anchor.TopLeft)
{
Margin = new MarginPadding { Top = 12f * 2f + 4f }, // +4 to account for the extra spaces above the digits.
WireframeOpacity = { BindTarget = WireframeOpacity },
Scale = new Vector2(0.5f),
},
new ArgonCounterTextComponent(Anchor.TopLeft)
{
Text = @"%",
Margin = new MarginPadding { Top = 12f },
WireframeOpacity = { BindTarget = WireframeOpacity }
},
}
};
}
}
}
}
61 changes: 61 additions & 0 deletions osu.Game/Screens/Play/HUD/ArgonComboCounter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
using osuTK;
using osuTK.Graphics;

namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonComboCounter : ComboCounter
{
private ArgonCounterTextComponent text = null!;

protected override double RollingDuration => 500;
protected override Easing RollingEasing => Easing.OutQuint;

[SettingSource("Wireframe opacity", "Controls the opacity of the wire frames behind the digits.")]
public BindableFloat WireframeOpacity { get; } = new BindableFloat(0.25f)
{
Precision = 0.01f,
MinValue = 0,
MaxValue = 1,
};

[BackgroundDependencyLoader]
private void load(ScoreProcessor scoreProcessor)
{
Current.BindTo(scoreProcessor.Combo);
Current.BindValueChanged(combo =>
{
bool wasIncrease = combo.NewValue > combo.OldValue;
bool wasMiss = combo.OldValue > 1 && combo.NewValue == 0;
float newScale = Math.Clamp(text.NumberContainer.Scale.X * (wasIncrease ? 1.1f : 0.8f), 0.6f, 1.4f);
float duration = wasMiss ? 2000 : 500;
text.NumberContainer
.ScaleTo(new Vector2(newScale))
.ScaleTo(Vector2.One, duration, Easing.OutQuint);
if (wasMiss)
text.FlashColour(Color4.Red, duration, Easing.OutQuint);
});
}

protected override LocalisableString FormatCount(int count) => $@"{count}x";

protected override IHasText CreateText() => text = new ArgonCounterTextComponent(Anchor.TopLeft, "COMBO")
{
WireframeOpacity = { BindTarget = WireframeOpacity },
};
}
}
171 changes: 171 additions & 0 deletions osu.Game/Screens/Play/HUD/ArgonCounterTextComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// 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 System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Framework.Text;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK;

namespace osu.Game.Screens.Play.HUD
{
public partial class ArgonCounterTextComponent : CompositeDrawable, IHasText
{
private readonly ArgonCounterSpriteText wireframesPart;
private readonly ArgonCounterSpriteText textPart;
private readonly OsuSpriteText labelText;

public IBindable<float> WireframeOpacity { get; } = new BindableFloat();
public Bindable<int> RequiredDisplayDigits { get; } = new BindableInt();

public Container NumberContainer { get; private set; }

public LocalisableString Text
{
get => textPart.Text;
set
{
int remainingCount = RequiredDisplayDigits.Value - value.ToString().Count(char.IsDigit);
string remainingText = remainingCount > 0 ? new string('#', remainingCount) : string.Empty;

wireframesPart.Text = remainingText + value;
textPart.Text = value;
}
}

public ArgonCounterTextComponent(Anchor anchor, LocalisableString? label = null)
{
Anchor = anchor;
Origin = anchor;
AutoSizeAxes = Axes.Both;

InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
labelText = new OsuSpriteText
{
Alpha = label != null ? 1 : 0,
Text = label.GetValueOrDefault(),
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.Bold),
Margin = new MarginPadding { Left = 2.5f },
},
NumberContainer = new Container
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
wireframesPart = new ArgonCounterSpriteText(wireframesLookup)
{
Anchor = anchor,
Origin = anchor,
},
textPart = new ArgonCounterSpriteText(textLookup)
{
Anchor = anchor,
Origin = anchor,
},
}
}
}
};
}

private string textLookup(char c)
{
switch (c)
{
case '.':
return @"dot";

case '%':
return @"percentage";

default:
return c.ToString();
}
}

private string wireframesLookup(char c)
{
if (c == '.') return @"dot";

return @"wireframes";
}

[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
labelText.Colour = colours.Blue0;
}

protected override void LoadComplete()
{
base.LoadComplete();
WireframeOpacity.BindValueChanged(v => wireframesPart.Alpha = v.NewValue, true);
}

private partial class ArgonCounterSpriteText : OsuSpriteText
{
private readonly Func<char, string> getLookup;

private GlyphStore glyphStore = null!;

protected override char FixedWidthReferenceCharacter => '5';

public ArgonCounterSpriteText(Func<char, string> getLookup)
{
this.getLookup = getLookup;

Shadow = false;
UseFullGlyphHeight = false;
}

[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
Spacing = new Vector2(-2f, 0f);
Font = new FontUsage(@"argon-counter", 1);
glyphStore = new GlyphStore(skin, getLookup);
}

protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore);

private class GlyphStore : ITexturedGlyphLookupStore
{
private readonly ISkin skin;
private readonly Func<char, string> getLookup;

public GlyphStore(ISkin skin, Func<char, string> getLookup)
{
this.skin = skin;
this.getLookup = getLookup;
}

public ITexturedCharacterGlyph? Get(string fontName, char character)
{
string lookup = getLookup(character);
var texture = skin.GetTexture($"{fontName}-{lookup}");

if (texture == null)
return null;

return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, texture.Height, null), texture, 0.125f);
}

public Task<ITexturedCharacterGlyph?> GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character));
}
}
}
}
Loading

0 comments on commit b995c96

Please sign in to comment.