diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs index 88a037afee..a8e4382ebb 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseTaikoPlayfield.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; @@ -11,20 +12,21 @@ using osu.Game.Modes.Taiko.Judgements; using osu.Game.Modes.Taiko.Objects; using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.UI; +using System; namespace osu.Desktop.VisualTests.Tests { internal class TestCaseTaikoPlayfield : TestCase { - public override string Description => "Taiko playfield"; + private const double default_duration = 300; + private const float scroll_time = 1000; - private TaikoPlayfield playfield; + public override string Description => "Taiko playfield"; protected override double TimePerAction => default_duration * 2; - private const double default_duration = 300; - - private const float scroll_time = 1000; + private readonly Random rng = new Random(1337); + private TaikoPlayfield playfield; public override void Reset() { @@ -41,7 +43,11 @@ namespace osu.Desktop.VisualTests.Tests AddStep("Strong Rim", () => addRimHit(true)); AddStep("Add bar line", () => addBarLine(false)); AddStep("Add major bar line", () => addBarLine(true)); - + AddStep("Height test 1", () => changePlayfieldSize(1)); + AddStep("Height test 2", () => changePlayfieldSize(2)); + AddStep("Height test 3", () => changePlayfieldSize(3)); + AddStep("Height test 4", () => changePlayfieldSize(4)); + AddStep("Height test 5", () => changePlayfieldSize(5)); var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; @@ -57,21 +63,53 @@ namespace osu.Desktop.VisualTests.Tests }); } + private void changePlayfieldSize(int step) + { + switch (step) + { + case 1: + addCentreHit(false); + break; + case 2: + addCentreHit(true); + break; + case 3: + addDrumRoll(false); + break; + case 4: + addDrumRoll(true); + break; + case 5: + addSwell(1000); + playfield.Delay(scroll_time - 100); + break; + } + + playfield.ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); + } + private void addHitJudgement() { TaikoHitResult hitResult = RNG.Next(2) == 0 ? TaikoHitResult.Good : TaikoHitResult.Great; - playfield.OnJudgement(new DrawableTestHit(new Hit()) + var h = new DrawableTestHit(new Hit()) { X = RNG.NextSingle(hitResult == TaikoHitResult.Good ? -0.1f : -0.05f, hitResult == TaikoHitResult.Good ? 0.1f : 0.05f), Judgement = new TaikoJudgement { Result = HitResult.Hit, TaikoResult = hitResult, - TimeOffset = 0, - SecondHit = RNG.Next(10) == 0 + TimeOffset = 0 } - }); + }; + + playfield.OnJudgement(h); + + if (RNG.Next(10) == 0) + { + h.Judgement.SecondHit = true; + playfield.OnJudgement(h); + } } private void addMissJudgement() diff --git a/osu.Game.Modes.Catch/UI/CatchPlayfield.cs b/osu.Game.Modes.Catch/UI/CatchPlayfield.cs index cf1a665470..f8792a7fd5 100644 --- a/osu.Game.Modes.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Modes.Catch/UI/CatchPlayfield.cs @@ -14,8 +14,7 @@ namespace osu.Game.Modes.Catch.UI { public CatchPlayfield() { - RelativeSizeAxes = Axes.Y; - Size = new Vector2(512, 0.9f); + Size = new Vector2(1, 0.9f); Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; diff --git a/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs b/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs index 670d18f71f..deb4ebac25 100644 --- a/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Modes.Mania/UI/ManiaPlayfield.cs @@ -15,8 +15,7 @@ namespace osu.Game.Modes.Mania.UI { public ManiaPlayfield(int columns) { - RelativeSizeAxes = Axes.Both; - Size = new Vector2(columns / 20f, 1f); + Size = new Vector2(0.8f, 1f); Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; diff --git a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs b/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs index ca9ff6fc61..7e314c5ba1 100644 --- a/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs +++ b/osu.Game.Modes.Osu/UI/OsuHitRenderer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK; using osu.Game.Beatmaps; using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Osu.Beatmaps; @@ -46,5 +47,7 @@ namespace osu.Game.Modes.Osu.UI return new DrawableSpinner(spinner); return null; } + + protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); } } diff --git a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs index d89bbfd131..4164607b4d 100644 --- a/osu.Game.Modes.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Modes.Osu/UI/OsuPlayfield.cs @@ -38,8 +38,6 @@ namespace osu.Game.Modes.Osu.UI { Anchor = Anchor.Centre; Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(0.75f); Add(new Drawable[] { diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs index e1a590a025..1e440df69a 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/DrawableSwell.cs @@ -65,7 +65,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), BlendingMode = BlendingMode.Additive, Masking = true, Children = new [] @@ -82,7 +82,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables Name = "Target ring (thick border)", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), Masking = true, BorderThickness = target_ring_thick_border, BlendingMode = BlendingMode.Additive, diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index f921511e22..216e05ebc4 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -19,15 +19,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces /// public class CirclePiece : TaikoPiece { - public const float SYMBOL_SIZE = TaikoHitObject.CIRCLE_RADIUS * 2f * 0.45f; + public const float SYMBOL_SIZE = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER * 0.45f; public const float SYMBOL_BORDER = 8; public const float SYMBOL_INNER_SIZE = SYMBOL_SIZE - 2 * SYMBOL_BORDER; - /// - /// The amount to scale up the base circle to show it as a "strong" piece. - /// - private const float strong_scale = 1.5f; - /// /// The colour of the inner circle and outer glows. /// @@ -129,10 +124,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces if (isStrong) { - Size *= strong_scale; + Size *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE; //default for symbols etc. - Content.Scale *= strong_scale; + Content.Scale *= TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE; } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs index a0c8865c59..2220438a4a 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -39,7 +39,7 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces public TaikoPiece() { //just a default - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER); } } } diff --git a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs index 697102eb22..53e795e2e2 100644 --- a/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs +++ b/osu.Game.Modes.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -15,12 +15,12 @@ namespace osu.Game.Modes.Taiko.Objects.Drawables.Pieces /// Any tick that is not the first for a drumroll is not filled, but is instead displayed /// as a hollow circle. This is what controls the border width of that circle. /// - private const float tick_border_width = TaikoHitObject.CIRCLE_RADIUS / 2 / 4; + private const float tick_border_width = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 16; /// /// The size of a tick. /// - private const float tick_size = TaikoHitObject.CIRCLE_RADIUS / 2; + private const float tick_size = TaikoHitObject.DEFAULT_CIRCLE_DIAMETER / 4; private bool filled; public bool Filled diff --git a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs index 54ab8c5300..ebc9b19d3a 100644 --- a/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Modes.Taiko/Objects/TaikoHitObject.cs @@ -4,15 +4,31 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Database; using osu.Game.Modes.Objects; +using osu.Game.Modes.Taiko.UI; namespace osu.Game.Modes.Taiko.Objects { public abstract class TaikoHitObject : HitObject { /// - /// HitCircle radius. + /// Diameter of a circle relative to the size of the . /// - public const float CIRCLE_RADIUS = 42f; + public const float PLAYFIELD_RELATIVE_DIAMETER = 0.5f; + + /// + /// Scale multiplier for a strong circle. + /// + public const float STRONG_CIRCLE_DIAMETER_SCALE = 1.5f; + + /// + /// Default circle diameter. + /// + public const float DEFAULT_CIRCLE_DIAMETER = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT * PLAYFIELD_RELATIVE_DIAMETER; + + /// + /// Default strong circle diameter. + /// + public const float DEFAULT_STRONG_CIRCLE_DIAMETER = DEFAULT_CIRCLE_DIAMETER * STRONG_CIRCLE_DIAMETER_SCALE; /// /// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a of 1000ms. diff --git a/osu.Game.Modes.Taiko/UI/HitExplosion.cs b/osu.Game.Modes.Taiko/UI/HitExplosion.cs index eb43c1a5d0..e4e329523f 100644 --- a/osu.Game.Modes.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Modes.Taiko/UI/HitExplosion.cs @@ -18,11 +18,6 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitExplosion : CircularContainer { - /// - /// The size multiplier of a hit explosion if a hit object has been hit with the second key. - /// - private const float secondhit_size_multiplier = 1.5f; - /// /// The judgement this hit explosion visualises. /// @@ -34,7 +29,7 @@ namespace osu.Game.Modes.Taiko.UI { Judgement = judgement; - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2); + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -85,7 +80,7 @@ namespace osu.Game.Modes.Taiko.UI /// public void VisualiseSecondHit() { - ResizeTo(Size * secondhit_size_multiplier, 50); + ResizeTo(Size * TaikoHitObject.STRONG_CIRCLE_DIAMETER_SCALE, 50); } } } diff --git a/osu.Game.Modes.Taiko/UI/HitTarget.cs b/osu.Game.Modes.Taiko/UI/HitTarget.cs index a17480628d..b22dc1d647 100644 --- a/osu.Game.Modes.Taiko/UI/HitTarget.cs +++ b/osu.Game.Modes.Taiko/UI/HitTarget.cs @@ -15,16 +15,6 @@ namespace osu.Game.Modes.Taiko.UI /// internal class HitTarget : Container { - /// - /// Diameter of normal hit object circles. - /// - private const float normal_diameter = TaikoHitObject.CIRCLE_RADIUS * 2; - - /// - /// Diameter of strong hit object circles. - /// - private const float strong_hit_diameter = normal_diameter * 1.5f; - /// /// The 1px inner border of the taiko playfield. /// @@ -37,7 +27,7 @@ namespace osu.Game.Modes.Taiko.UI public HitTarget() { - RelativeSizeAxes = Axes.Y; + Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT); Children = new Drawable[] { @@ -47,7 +37,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset), Alpha = 0.1f }, new CircularContainer @@ -55,7 +45,7 @@ namespace osu.Game.Modes.Taiko.UI Name = "Strong Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(strong_hit_diameter), + Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -75,7 +65,7 @@ namespace osu.Game.Modes.Taiko.UI Name = "Normal Hit Ring", Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(normal_diameter), + Size = new Vector2(TaikoHitObject.DEFAULT_CIRCLE_DIAMETER), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -96,7 +86,7 @@ namespace osu.Game.Modes.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Y = -border_offset, - Size = new Vector2(border_thickness, (TaikoPlayfield.PLAYFIELD_HEIGHT - strong_hit_diameter) / 2f - border_offset), + Size = new Vector2(border_thickness, (TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT - TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER) / 2f - border_offset), Alpha = 0.1f }, }; diff --git a/osu.Game.Modes.Taiko/UI/InputDrum.cs b/osu.Game.Modes.Taiko/UI/InputDrum.cs index 0c1e1105cb..d238c38e74 100644 --- a/osu.Game.Modes.Taiko/UI/InputDrum.cs +++ b/osu.Game.Modes.Taiko/UI/InputDrum.cs @@ -21,7 +21,7 @@ namespace osu.Game.Modes.Taiko.UI { public InputDrum() { - Size = new Vector2(TaikoPlayfield.PLAYFIELD_HEIGHT); + Size = new Vector2(TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT); const float middle_split = 10; diff --git a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs index 29fa693d58..32476dff7f 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoHitRenderer.cs @@ -17,6 +17,7 @@ using osu.Game.Modes.Taiko.Objects.Drawables; using osu.Game.Modes.Taiko.Scoring; using osu.Game.Modes.UI; using osu.Game.Modes.Taiko.Replays; +using OpenTK; namespace osu.Game.Modes.Taiko.UI { @@ -100,6 +101,17 @@ namespace osu.Game.Modes.Taiko.UI } } + protected override Vector2 GetPlayfieldAspectAdjust() + { + const float default_relative_height = TaikoPlayfield.DEFAULT_PLAYFIELD_HEIGHT / 768; + const float default_aspect = 16f / 9f; + + float aspectAdjust = MathHelper.Clamp(DrawWidth / DrawHeight, 0.4f, 4) / default_aspect; + + return new Vector2(1, default_relative_height * aspectAdjust); + } + + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); protected override IBeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(); diff --git a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs index 9e7eb571a1..db3a1bc84e 100644 --- a/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Modes.Taiko/UI/TaikoPlayfield.cs @@ -16,21 +16,21 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Primitives; using System.Linq; using osu.Game.Modes.Taiko.Objects.Drawables; +using System; namespace osu.Game.Modes.Taiko.UI { public class TaikoPlayfield : Playfield { /// - /// The play field height. This is relative to the size of hit objects - /// such that the playfield is just a bit larger than strong hits. + /// The default play field height. /// - public const float PLAYFIELD_HEIGHT = TaikoHitObject.CIRCLE_RADIUS * 2 * 2; + public const float DEFAULT_PLAYFIELD_HEIGHT = 168f; /// /// The offset from which the center of the hit target lies at. /// - private const float hit_target_offset = TaikoHitObject.CIRCLE_RADIUS * 1.5f + 40; + private const float hit_target_offset = TaikoHitObject.DEFAULT_STRONG_CIRCLE_DIAMETER / 2f + 40; /// /// The size of the left area of the playfield. This area contains the input drum. @@ -52,13 +52,11 @@ namespace osu.Game.Modes.Taiko.UI public TaikoPlayfield() { - RelativeSizeAxes = Axes.X; - Height = PLAYFIELD_HEIGHT; - AddInternal(new Drawable[] { rightBackgroundContainer = new Container { + Name = "Transparent playfield background", RelativeSizeAxes = Axes.Both, BorderThickness = 2, Masking = true, @@ -77,76 +75,88 @@ namespace osu.Game.Modes.Taiko.UI }, } }, - new Container + new ScaleFixContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_HEIGHT, + Children = new[] { new Container { - X = hit_target_offset, + Name = "Transparent playfield elements", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, Children = new Drawable[] { - hitExplosionContainer = new Container + new Container { - Anchor = Anchor.CentreLeft, + Name = "Hit target container", + X = hit_target_offset, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + BlendingMode = BlendingMode.Additive + }, + barLineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + }, + hitObjectContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }, + judgementContainer = new Container + { + RelativeSizeAxes = Axes.Y, + BlendingMode = BlendingMode.Additive + }, + }, + }, + } + }, + leftBackgroundContainer = new Container + { + Name = "Left overlay", + Size = new Vector2(left_area_size, DEFAULT_PLAYFIELD_HEIGHT), + BorderThickness = 1, + Children = new Drawable[] + { + leftBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum + { + Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(TaikoHitObject.CIRCLE_RADIUS * 2), - BlendingMode = BlendingMode.Additive + RelativePositionAxes = Axes.X, + Position = new Vector2(0.10f, 0), + Scale = new Vector2(0.9f) }, - barLineContainer = new Container + new Box { - RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), }, - new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - }, - hitObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - judgementContainer = new Container - { - RelativeSizeAxes = Axes.Both, - BlendingMode = BlendingMode.Additive - }, - }, - }, - } - }, - leftBackgroundContainer = new Container - { - Size = new Vector2(left_area_size, PLAYFIELD_HEIGHT), - BorderThickness = 1, - Children = new Drawable[] - { - leftBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - Position = new Vector2(0.10f, 0), - Scale = new Vector2(0.9f) - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - ColourInfo = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + } }, } }, topLevelHitContainer = new Container { + Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, } }); @@ -208,5 +218,56 @@ namespace osu.Game.Modes.Taiko.UI else hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit(); } + + /// + /// This is a very special type of container. It serves a similar purpose to , however unlike , + /// this will only adjust the scale relative to the height of its parent and will maintain the original width relative to its parent. + /// + /// + /// By adjusting the scale relative to the height of its parent, the aspect ratio of this container's children is maintained, however this is undesirable + /// in the case where the hit object container should not have its width adjusted by scale. To counteract this, another container is nested inside this + /// container which takes care of reversing the width adjustment while appearing transparent to the user. + /// + /// + private class ScaleFixContainer : Container + { + protected override Container Content => widthAdjustmentContainer; + private readonly WidthAdjustmentContainer widthAdjustmentContainer; + + /// + /// We only want to apply DrawScale in the Y-axis to preserve aspect ratio and doesn't care about having its width adjusted. + /// + protected override Vector2 DrawScale => Scale * RelativeToAbsoluteFactor.Y / DrawHeight; + + public ScaleFixContainer() + { + AddInternal(widthAdjustmentContainer = new WidthAdjustmentContainer { ParentDrawScaleReference = () => DrawScale.X }); + } + + /// + /// The container type that reverses the width adjustment. + /// + private class WidthAdjustmentContainer : Container + { + /// + /// This container needs to know its parent's so it can reverse the width adjustment caused by . + /// + public Func ParentDrawScaleReference; + + public WidthAdjustmentContainer() + { + // This container doesn't care about height, it should always fill its parent + RelativeSizeAxes = Axes.Y; + } + + protected override void Update() + { + base.Update(); + + // Reverse the DrawScale adjustment + Width = Parent.DrawSize.X / ParentDrawScaleReference(); + } + } + } } } \ No newline at end of file diff --git a/osu.Game/Modes/UI/HitRenderer.cs b/osu.Game/Modes/UI/HitRenderer.cs index a958c61c68..afc525d686 100644 --- a/osu.Game/Modes/UI/HitRenderer.cs +++ b/osu.Game/Modes/UI/HitRenderer.cs @@ -16,6 +16,7 @@ using System.Diagnostics; using System.Linq; using osu.Game.Modes.Replays; using osu.Game.Modes.Scoring; +using OpenTK; namespace osu.Game.Modes.UI { @@ -167,6 +168,11 @@ namespace osu.Game.Modes.UI { public event Action OnJudgement; + /// + /// Whether to apply adjustments to the child based on our own size. + /// + public bool AspectAdjust = true; + public sealed override bool ProvidingUserCursor => !HasReplayLoaded && Playfield.ProvidingUserCursor; protected override Container Content => content; @@ -219,6 +225,19 @@ namespace osu.Game.Modes.UI Playfield.PostProcess(); } + protected override void Update() + { + base.Update(); + + Playfield.Size = AspectAdjust ? GetPlayfieldAspectAdjust() : Vector2.One; + } + + /// + /// In some cases we want to apply changes to the relative size of our contained based on custom conditions. + /// + /// + protected virtual Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); //a sane default + /// /// Triggered when an object's Judgement is updated. /// diff --git a/osu.Game/Modes/UI/Playfield.cs b/osu.Game/Modes/UI/Playfield.cs index f31ee0f189..bf5f0acde5 100644 --- a/osu.Game/Modes/UI/Playfield.cs +++ b/osu.Game/Modes/UI/Playfield.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Modes.Objects; @@ -63,6 +64,12 @@ namespace osu.Game.Modes.UI Add(HitObjects); } + public override Axes RelativeSizeAxes + { + get { return Axes.Both; } + set { throw new InvalidOperationException($@"{nameof(Playfield)}'s {nameof(RelativeSizeAxes)} should never be changed from {Axes.Both}"); } + } + /// /// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield. ///