mirror of
https://github.com/ppy/osu.git
synced 2025-01-22 07:52:56 +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:
commit
e78f0bc89b
@ -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]
|
||||||
|
@ -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)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user