diff --git a/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs new file mode 100644 index 0000000000..07c7b4d1db --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/HitObjectApplicationTestScene.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public abstract class HitObjectApplicationTestScene : OsuTestScene + { + [Cached(typeof(IScrollingInfo))] + private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo + { + Direction = { Value = ScrollingDirection.Left }, + TimeRange = { Value = 1000 }, + }; + + private ScrollingHitObjectContainer hitObjectContainer; + + [SetUpSteps] + public void SetUp() + => AddStep("create SHOC", () => Child = hitObjectContainer = new ScrollingHitObjectContainer + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Clock = new FramedClock(new StopwatchClock()) + }); + + protected void AddHitObject(DrawableHitObject hitObject) + => AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject)); + + protected void RemoveHitObject(DrawableHitObject hitObject) + => AddStep("remove from SHOC", () => hitObjectContainer.Remove(hitObject)); + + protected TObject PrepareObject(TObject hitObject) + where TObject : TaikoHitObject + { + hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + return hitObject; + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs index f6aec20d53..ff309f278e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableBarLine.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning } }; - hoc.Add(new DrawableBarLineMajor(createBarLineAtCurrentTime(true)) + hoc.Add(new DrawableBarLine(createBarLineAtCurrentTime(true)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs new file mode 100644 index 0000000000..65230a07bc --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneBarLineApplication.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneBarLineApplication : HitObjectApplicationTestScene + { + [Test] + public void TestApplyNewBarLine() + { + DrawableBarLine barLine = new DrawableBarLine(PrepareObject(new BarLine + { + StartTime = 400, + Major = true + })); + + AddHitObject(barLine); + RemoveHitObject(barLine); + + AddStep("apply new bar line", () => barLine.Apply(PrepareObject(new BarLine + { + StartTime = 200, + Major = false + }), null)); + AddHitObject(barLine); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index c3fa03d404..7695ca067b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -145,9 +145,13 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay }; + BarLine bl = new BarLine + { + StartTime = DrawableRuleset.Playfield.Time.Current + delay, + Major = major + }; - DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + DrawableRuleset.Playfield.Add(bl); } private void addSwell(double duration = default_duration) diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index 6306195704..bbfc02f975 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class BarLine : TaikoHitObject, IBarLine { - public bool Major { get; set; } + public bool Major + { + get => MajorBindable.Value; + set => MajorBindable.Value = value; + } + + public readonly Bindable MajorBindable = new BindableBool(); public override Judgement CreateJudgement() => new IgnoreJudgement(); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index aadcc420df..9e50faabc1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -1,7 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects; using osuTK; @@ -15,49 +19,123 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// public class DrawableBarLine : DrawableHitObject { + public new BarLine HitObject => (BarLine)base.HitObject; + /// /// The width of the line tracker. /// private const float tracker_width = 2f; /// - /// Fade out time calibrated to a pre-empt of 1000ms. + /// The vertical offset of the triangles from the line tracker. /// - private const float base_fadeout_time = 100f; + private const float triangle_offset = 10f; + + /// + /// The size of the triangles. + /// + private const float triangle_size = 20f; /// /// The visual line tracker. /// - protected SkinnableDrawable Line; + private SkinnableDrawable line; /// - /// The bar line. + /// Container with triangles. Only visible for major lines. /// - protected readonly BarLine BarLine; + private Container triangleContainer; - public DrawableBarLine(BarLine barLine) + private readonly Bindable major = new Bindable(); + + public DrawableBarLine() + : this(null) + { + } + + public DrawableBarLine([CanBeNull] BarLine barLine) : base(barLine) { - BarLine = barLine; + } + [BackgroundDependencyLoader] + private void load() + { Anchor = Anchor.CentreLeft; Origin = Anchor.Centre; RelativeSizeAxes = Axes.Y; Width = tracker_width; - AddInternal(Line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box + AddRangeInternal(new Drawable[] { - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(0.5f, 0), - }) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0.75f, + line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box + { + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(0.5f, 0), + }) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + triangleContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new EquilateralTriangle + { + Name = "Top", + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, -triangle_offset), + Size = new Vector2(-triangle_size), + EdgeSmoothness = new Vector2(1), + }, + new EquilateralTriangle + { + Name = "Bottom", + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Position = new Vector2(0, triangle_offset), + Size = new Vector2(triangle_size), + EdgeSmoothness = new Vector2(1), + } + } + } }); } - protected override void UpdateHitStateTransforms(ArmedState state) => this.FadeOut(150); + protected override void LoadComplete() + { + base.LoadComplete(); + major.BindValueChanged(updateMajor); + } + + private void updateMajor(ValueChangedEvent major) + { + line.Alpha = major.NewValue ? 1f : 0.75f; + triangleContainer.Alpha = major.NewValue ? 1 : 0; + } + + protected override void OnApply() + { + base.OnApply(); + major.BindTo(HitObject.MajorBindable); + } + + protected override void OnFree() + { + base.OnFree(); + major.UnbindFrom(HitObject.MajorBindable); + } + + protected override void UpdateHitStateTransforms(ArmedState state) + { + using (BeginAbsoluteSequence(HitObject.StartTime)) + this.FadeOutFromOne(150).Expire(); + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs deleted file mode 100644 index 62aab3524b..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osuTK; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Rulesets.Taiko.Objects.Drawables -{ - public class DrawableBarLineMajor : DrawableBarLine - { - /// - /// The vertical offset of the triangles from the line tracker. - /// - private const float triangle_offfset = 10f; - - /// - /// The size of the triangles. - /// - private const float triangle_size = 20f; - - private readonly Container triangleContainer; - - public DrawableBarLineMajor(BarLine barLine) - : base(barLine) - { - AddInternal(triangleContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Children = new[] - { - new EquilateralTriangle - { - Name = "Top", - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Position = new Vector2(0, -triangle_offfset), - Size = new Vector2(-triangle_size), - EdgeSmoothness = new Vector2(1), - }, - new EquilateralTriangle - { - Name = "Bottom", - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre, - Position = new Vector2(0, triangle_offfset), - Size = new Vector2(triangle_size), - EdgeSmoothness = new Vector2(1), - } - } - }); - - Line.Alpha = 1f; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime)) - triangleContainer.FadeOut(150); - } - } -} diff --git a/osu.Game.Rulesets.Taiko/UI/BarLinePlayfield.cs b/osu.Game.Rulesets.Taiko/UI/BarLinePlayfield.cs new file mode 100644 index 0000000000..cb878e8ea0 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/BarLinePlayfield.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; + +namespace osu.Game.Rulesets.Taiko.UI +{ + public class BarLinePlayfield : ScrollingPlayfield + { + [BackgroundDependencyLoader] + private void load() + { + RegisterPool(15); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index e6aacf34dc..bbf8cb8de0 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar))); + new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar)); FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Scroller), _ => Empty()) { diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 370760f03e..d20b190c86 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -38,10 +39,15 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable mascot; private ProxyContainer topLevelHitContainer; - private ScrollingHitObjectContainer barlineContainer; private Container rightArea; private Container leftArea; + /// + /// is purposefully not called on this to prevent i.e. being able to interact + /// with bar lines in the editor. + /// + private BarLinePlayfield barLinePlayfield; + private Container hitTargetOffsetContent; public TaikoPlayfield(ControlPointInfo controlPoints) @@ -84,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - barlineContainer = new ScrollingHitObjectContainer(), + barLinePlayfield = new BarLinePlayfield(), new Container { Name = "Hit objects", @@ -155,12 +161,50 @@ namespace osu.Game.Rulesets.Taiko.UI mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); } + #region Pooling support + + public override void Add(HitObject h) + { + switch (h) + { + case BarLine barLine: + barLinePlayfield.Add(barLine); + break; + + case TaikoHitObject taikoHitObject: + base.Add(taikoHitObject); + break; + + default: + throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}"); + } + } + + public override bool Remove(HitObject h) + { + switch (h) + { + case BarLine barLine: + return barLinePlayfield.Remove(barLine); + + case TaikoHitObject taikoHitObject: + return base.Remove(taikoHitObject); + + default: + throw new ArgumentException($"Unsupported {nameof(HitObject)} type: {h.GetType()}"); + } + } + + #endregion + + #region Non-pooling support + public override void Add(DrawableHitObject h) { switch (h) { - case DrawableBarLine barline: - barlineContainer.Add(barline); + case DrawableBarLine barLine: + barLinePlayfield.Add(barLine); break; case DrawableTaikoHitObject taikoObject: @@ -170,7 +214,7 @@ namespace osu.Game.Rulesets.Taiko.UI break; default: - throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type"); + throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}"); } } @@ -178,8 +222,8 @@ namespace osu.Game.Rulesets.Taiko.UI { switch (h) { - case DrawableBarLine barline: - return barlineContainer.Remove(barline); + case DrawableBarLine barLine: + return barLinePlayfield.Remove(barLine); case DrawableTaikoHitObject _: h.OnNewResult -= OnNewResult; @@ -187,10 +231,12 @@ namespace osu.Game.Rulesets.Taiko.UI return base.Remove(h); default: - throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type"); + throw new ArgumentException($"Unsupported {nameof(DrawableHitObject)} type: {h.GetType()}"); } } + #endregion + internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { if (!DisplayJudgements.Value)