Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite osu!taiko playfield adjustment container to keep playfield height constant #26631

Merged
merged 7 commits into from
Jan 26, 2024
26 changes: 22 additions & 4 deletions osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoPlayfield.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;

namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
Expand All @@ -37,11 +39,14 @@ public void SetUpSteps()
Beatmap.Value.Track.Start();
});

AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield
AddStep("Load playfield", () => SetContents(_ => new Container
{
Height = 0.2f,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(2f, 1f),
Scale = new Vector2(0.5f),
Child = new TaikoPlayfieldAdjustmentContainer { Child = new TaikoPlayfield() },
}));
}

Expand All @@ -54,7 +59,20 @@ public void TestBasic()
[Test]
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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public HitPlacementBlueprint()
{
InternalChild = piece = new HitPiece
{
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT)
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ public TaikoSpanPlacementBlueprint(HitObject hitObject)
{
headPiece = new HitPiece
{
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT)
},
lengthPiece = new LengthPiece
{
Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT
Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT
},
tailPiece = new HitPiece
{
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.BASE_HEIGHT)
}
};
}
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPla
/// </param>
private Vector2 adjustSizeForPlayfieldAspectRatio(float size)
{
return new Vector2(0, size * taikoPlayfield.DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
return new Vector2(0, size * taikoPlayfield.Parent!.Scale.Y);
peppy marked this conversation as resolved.
Show resolved Hide resolved
}

protected override void UpdateFlashlightSize(float size)
Expand Down
50 changes: 16 additions & 34 deletions osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyHitTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,37 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
using osuTK;

namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
public partial class TaikoLegacyHitTarget : CompositeDrawable
{
private Container content = null!;

[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
RelativeSizeAxes = Axes.Both;

InternalChild = content = new Container
InternalChildren = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
new Sprite
{
Texture = skin.GetTexture("approachcircle"),
Scale = new Vector2(0.83f),
Alpha = 0.47f, // eyeballed to match stable
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Sprite
{
new Sprite
{
Texture = skin.GetTexture("approachcircle"),
Scale = new Vector2(0.83f),
Alpha = 0.47f, // eyeballed to match stable
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Sprite
{
Texture = skin.GetTexture("taikobigcircle"),
Scale = new Vector2(0.8f),
Alpha = 0.22f, // eyeballed to match stable
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
}
Texture = skin.GetTexture("taikobigcircle"),
Scale = new Vector2(0.8f),
Alpha = 0.22f, // eyeballed to match stable
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);
}
}
}
42 changes: 17 additions & 25 deletions osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,15 @@
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Skinning;
using osuTK;

namespace osu.Game.Rulesets.Taiko.UI
{
public partial class TaikoPlayfield : ScrollingPlayfield
{
/// <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>
public const float DEFAULT_HEIGHT = 200;
public const float BASE_HEIGHT = 200;

/// <summary>
/// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position.
Expand All @@ -44,7 +43,6 @@ public partial class TaikoPlayfield : ScrollingPlayfield
private JudgementContainer<DrawableTaikoJudgement> judgementContainer = null!;
private ScrollingHitObjectContainer drumRollHitContainer = null!;
internal Drawable HitTarget = null!;
private SkinnableDrawable mascot = null!;

private JudgementPooler<DrawableTaikoJudgement> judgementPooler = null!;
private readonly IDictionary<HitResult, HitExplosionPool> explosionPools = new Dictionary<HitResult, HitExplosionPool>();
Expand All @@ -59,13 +57,11 @@ public partial class TaikoPlayfield : ScrollingPlayfield
/// </remarks>
private BarLinePlayfield barLinePlayfield = null!;

private Container barLineContent = null!;
private Container hitObjectContent = null!;
private Container overlayContent = null!;

[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
const float hit_target_width = BASE_HEIGHT;

inputDrum = new InputDrum
{
Anchor = Anchor.CentreLeft,
Expand All @@ -89,7 +85,7 @@ private void load(OsuColour colours)
inputDrum.CreateProxy(),
}
},
mascot = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty())
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Mascot), _ => Empty())
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.TopLeft,
Expand All @@ -101,14 +97,13 @@ private void load(OsuColour colours)
{
Name = "Right area",
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
Name = "Elements before hit objects",
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line alone sells me on this PR.

Name = "Elements behind hit objects",
RelativeSizeAxes = Axes.Y,
Width = hit_target_width,
Children = new[]
{
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty())
Expand All @@ -125,10 +120,11 @@ private void load(OsuColour colours)
}
}
},
barLineContent = new Container
new Container
{
Name = "Bar line content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = hit_target_width / 2 },
Children = new Drawable[]
{
UnderlayElements = new Container
Expand All @@ -138,17 +134,19 @@ private void load(OsuColour colours)
barLinePlayfield = new BarLinePlayfield(),
}
},
hitObjectContent = new Container
new Container
{
Name = "Masked hit objects content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = hit_target_width / 2 },
Masking = true,
Child = HitObjectContainer,
},
overlayContent = new Container
new Container
{
Name = "Elements after hit objects",
Name = "Overlay content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = hit_target_width / 2 },
Children = new Drawable[]
{
drumRollHitContainer = new DrumRollHitContainer(),
Expand Down Expand Up @@ -226,14 +224,8 @@ protected override void Update()
{
base.Update();

// Padding is required to be updated for elements which are based on "absolute" X sized elements.
// This is basically allowing for correct alignment as relative pieces move around them.
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);
// todo: input drum width should be constant.
rightArea.Padding = new MarginPadding { Left = inputDrum.DrawWidth };
}

#region Pooling support
Expand Down
27 changes: 18 additions & 9 deletions osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,31 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.UI;
using osuTK;

namespace osu.Game.Rulesets.Taiko.UI
{
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 MINIMUM_ASPECT = 5f / 4f;

public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);

public TaikoPlayfieldAdjustmentContainer()
{
RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y;
Height = TaikoPlayfield.BASE_HEIGHT;
}

protected override void 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.
// We originally wanted to limit this more, but there was considerable pushback from the community.
Expand All @@ -33,19 +41,20 @@ protected override void Update()
float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y;

if (currentAspect > MAXIMUM_ASPECT)
height *= currentAspect / MAXIMUM_ASPECT;
relativeHeight *= currentAspect / MAXIMUM_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.
height = Math.Min(height, 1f / 3f);
Height = height;
relativeHeight = Math.Min(relativeHeight, 1f / 3f);

// 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.
RelativePositionAxes = Axes.Y;
Y = height;
Y = relativeHeight;

Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f));
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this can be simplified, but not going to attempt that until we're first in a good state (ie. your PRs merged + #25919 is resolved).

Width = 1 / Scale.X;
}
}
}
Loading