1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 15:27:24 +08:00

Merge pull request #26631 from frenzibyte/refactor-taiko-playfield-layout

Rewrite osu!taiko playfield adjustment container to keep playfield height constant
This commit is contained in:
Dean Herbert 2024-01-26 17:17:49 +09:00 committed by GitHub
commit e78f0bc89b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 77 deletions

View File

@ -1,17 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{ {
@ -37,11 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Beatmap.Value.Track.Start(); Beatmap.Value.Track.Start();
}); });
AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield AddStep("Load playfield", () => SetContents(_ => new Container
{ {
Height = 0.2f,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(2f, 1f),
Scale = new Vector2(0.5f),
Child = new TaikoPlayfieldAdjustmentContainer { Child = new TaikoPlayfield() },
})); }));
} }
@ -54,7 +59,20 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
[Test] [Test]
public void TestHeightChanges() public void TestHeightChanges()
{ {
AddRepeatStep("change height", () => this.ChildrenOfType<TaikoPlayfield>().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50); int value = 0;
AddRepeatStep("change height", () =>
{
value = (value + 1) % 5;
this.ChildrenOfType<TaikoPlayfieldAdjustmentContainer>().ForEach(p =>
{
var parent = (Container)p.Parent.AsNonNull();
parent.Scale = new Vector2(0.5f + 0.1f * value);
parent.Width = 1f / parent.Scale.X;
parent.Height = 0.5f / parent.Scale.Y;
});
}, 50);
} }
[Test] [Test]

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{ {
InternalChild = piece = new HitPiece InternalChild = piece = new HitPiece
{ {
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT)
}; };
} }

View File

@ -39,15 +39,15 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{ {
headPiece = new HitPiece headPiece = new HitPiece
{ {
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT)
}, },
lengthPiece = new LengthPiece lengthPiece = new LengthPiece
{ {
Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT
}, },
tailPiece = new HitPiece tailPiece = new HitPiece
{ {
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT)
} }
}; };
} }

View File

@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
/// </param> /// </param>
private Vector2 adjustSizeForPlayfieldAspectRatio(float size) private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
{ {
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT); return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y);
} }
protected override void UpdateFlashlightSize(float size) protected override void UpdateFlashlightSize(float size)

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -13,47 +12,30 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{ {
public partial class TaikoLegacyHitTarget : CompositeDrawable public partial class TaikoLegacyHitTarget : CompositeDrawable
{ {
private Container content = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource skin) private void load(ISkinSource skin)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
InternalChild = content = new Container InternalChildren = new Drawable[]
{ {
RelativeSizeAxes = Axes.Both, new Sprite
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{ {
new Sprite Texture = skin.GetTexture("approachcircle"),
{ Scale = new Vector2(0.83f),
Texture = skin.GetTexture("approachcircle"), Alpha = 0.47f, // eyeballed to match stable
Scale = new Vector2(0.83f), Anchor = Anchor.Centre,
Alpha = 0.47f, // eyeballed to match stable Origin = Anchor.Centre,
Anchor = Anchor.Centre, },
Origin = Anchor.Centre, new Sprite
}, {
new Sprite Texture = skin.GetTexture("taikobigcircle"),
{ Scale = new Vector2(0.8f),
Texture = skin.GetTexture("taikobigcircle"), Alpha = 0.22f, // eyeballed to match stable
Scale = new Vector2(0.8f), Anchor = Anchor.Centre,
Alpha = 0.22f, // eyeballed to match stable Origin = Anchor.Centre,
Anchor = Anchor.Centre, },
Origin = Anchor.Centre,
},
}
}; };
} }
protected override void Update()
{
base.Update();
// Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements.
// This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement.
content.Scale = new Vector2(DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
} }
} }

View File

@ -21,16 +21,15 @@ using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
public partial class TaikoPlayfield : ScrollingPlayfield public partial class TaikoPlayfield : ScrollingPlayfield
{ {
/// <summary> /// <summary>
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>. /// Base height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
/// </summary> /// </summary>
public const float DEFAULT_HEIGHT = 200; public const float BASE_HEIGHT = 200;
/// <summary> /// <summary>
/// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position. /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position.
@ -44,7 +43,6 @@ namespace osu.Game.Rulesets.Taiko.UI
private JudgementContainer<DrawableTaikoJudgement> judgementContainer = null!; private JudgementContainer<DrawableTaikoJudgement> judgementContainer = null!;
private ScrollingHitObjectContainer drumRollHitContainer = null!; private ScrollingHitObjectContainer drumRollHitContainer = null!;
internal Drawable HitTarget = null!; internal Drawable HitTarget = null!;
private SkinnableDrawable mascot = null!;
private JudgementPooler<DrawableTaikoJudgement> judgementPooler = null!; private JudgementPooler<DrawableTaikoJudgement> judgementPooler = null!;
private readonly IDictionary<HitResult, HitExplosionPool> explosionPools = new Dictionary<HitResult, HitExplosionPool>(); private readonly IDictionary<HitResult, HitExplosionPool> explosionPools = new Dictionary<HitResult, HitExplosionPool>();
@ -59,13 +57,11 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </remarks> /// </remarks>
private BarLinePlayfield barLinePlayfield = null!; private BarLinePlayfield barLinePlayfield = null!;
private Container barLineContent = null!;
private Container hitObjectContent = null!;
private Container overlayContent = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
const float hit_target_width = BASE_HEIGHT;
inputDrum = new InputDrum inputDrum = new InputDrum
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
@ -89,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.UI
inputDrum.CreateProxy(), inputDrum.CreateProxy(),
} }
}, },
mascot = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty()) new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty())
{ {
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
@ -101,14 +97,13 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
Name = "Right area", Name = "Right area",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
{ {
Name = "Elements before hit objects", Name = "Elements behind hit objects",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Y,
FillMode = FillMode.Fit, Width = hit_target_width,
Children = new[] Children = new[]
{ {
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty()) new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty())
@ -125,10 +120,11 @@ namespace osu.Game.Rulesets.Taiko.UI
} }
} }
}, },
barLineContent = new Container new Container
{ {
Name = "Bar line content", Name = "Bar line content",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = hit_target_width / 2 },
Children = new Drawable[] Children = new Drawable[]
{ {
UnderlayElements = new Container UnderlayElements = new Container
@ -138,17 +134,19 @@ namespace osu.Game.Rulesets.Taiko.UI
barLinePlayfield = new BarLinePlayfield(), barLinePlayfield = new BarLinePlayfield(),
} }
}, },
hitObjectContent = new Container new Container
{ {
Name = "Masked hit objects content", Name = "Masked hit objects content",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = hit_target_width / 2 },
Masking = true, Masking = true,
Child = HitObjectContainer, Child = HitObjectContainer,
}, },
overlayContent = new Container new Container
{ {
Name = "Elements after hit objects", Name = "Overlay content",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = hit_target_width / 2 },
Children = new Drawable[] Children = new Drawable[]
{ {
drumRollHitContainer = new DrumRollHitContainer(), drumRollHitContainer = new DrumRollHitContainer(),
@ -226,14 +224,8 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
base.Update(); base.Update();
// Padding is required to be updated for elements which are based on "absolute" X sized elements. // todo: input drum width should be constant.
// This is basically allowing for correct alignment as relative pieces move around them. rightArea.Padding = new MarginPadding { Left = inputDrum.DrawWidth };
rightArea.Padding = new MarginPadding { Left = inputDrum.Width };
barLineContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
hitObjectContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
overlayContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT);
} }
#region Pooling support #region Pooling support

View File

@ -5,23 +5,31 @@ using System;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
{ {
private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
public const float MAXIMUM_ASPECT = 16f / 9f; public const float MAXIMUM_ASPECT = 16f / 9f;
public const float MINIMUM_ASPECT = 5f / 4f; public const float MINIMUM_ASPECT = 5f / 4f;
public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true); public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);
public TaikoPlayfieldAdjustmentContainer()
{
RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y;
Height = TaikoPlayfield.BASE_HEIGHT;
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
float height = default_relative_height; const float base_relative_height = TaikoPlayfield.BASE_HEIGHT / 768;
float relativeHeight = base_relative_height;
// Players coming from stable expect to be able to change the aspect ratio regardless of the window size. // Players coming from stable expect to be able to change the aspect ratio regardless of the window size.
// We originally wanted to limit this more, but there was considerable pushback from the community. // We originally wanted to limit this more, but there was considerable pushback from the community.
@ -33,19 +41,20 @@ namespace osu.Game.Rulesets.Taiko.UI
float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y; float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y;
if (currentAspect > MAXIMUM_ASPECT) if (currentAspect > MAXIMUM_ASPECT)
height *= currentAspect / MAXIMUM_ASPECT; relativeHeight *= currentAspect / MAXIMUM_ASPECT;
else if (currentAspect < MINIMUM_ASPECT) else if (currentAspect < MINIMUM_ASPECT)
height *= currentAspect / MINIMUM_ASPECT; relativeHeight *= currentAspect / MINIMUM_ASPECT;
} }
// Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions. // Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions.
height = Math.Min(height, 1f / 3f); relativeHeight = Math.Min(relativeHeight, 1f / 3f);
Height = height;
// Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it. // Position the taiko playfield exactly one playfield from the top of the screen, if there is enough space for it.
// Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered. // Note that the relative height cannot exceed one-third - if that limit is hit, the playfield will be exactly centered.
RelativePositionAxes = Axes.Y; Y = relativeHeight;
Y = height;
Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f));
Width = 1 / Scale.X;
} }
} }
} }