diff --git a/osu.Desktop.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Desktop.Tests/Visual/TestCaseScrollingHitObjects.cs index 056e063120..6b30cc4f32 100644 --- a/osu.Desktop.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Desktop.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -13,6 +13,9 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; using OpenTK; using OpenTK.Graphics; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Judgements; +using System; namespace osu.Desktop.Tests.Visual { @@ -28,7 +31,7 @@ namespace osu.Desktop.Tests.Visual public TestCaseScrollingHitObjects() { OsuSpriteText timeRangeText; - SpeedAdjustmentCollection adjustmentCollection; + ScrollingPlayfield.ScrollingHitObjectContainer scrollingHitObjectContainer; timeRangeBindable = new BindableDouble(2000) { @@ -68,7 +71,7 @@ namespace osu.Desktop.Tests.Visual RelativeSizeAxes = Axes.Both, Alpha = 0.25f }, - adjustmentCollection = new SpeedAdjustmentCollection(Axes.Y) + scrollingHitObjectContainer = new ScrollingPlayfield.ScrollingHitObjectContainer(Axes.Y) { RelativeSizeAxes = Axes.Both, VisibleTimeRange = timeRangeBindable, @@ -109,9 +112,9 @@ namespace osu.Desktop.Tests.Visual timeRangeBindable.TriggerChange(); - adjustmentCollection.Add(new TestSpeedAdjustmentContainer(new MultiplierControlPoint())); + scrollingHitObjectContainer.AddSpeedAdjustment(new TestSpeedAdjustmentContainer(new MultiplierControlPoint())); - AddStep("Add hit object", () => adjustmentCollection.Add(new TestDrawableHitObject(new HitObject { StartTime = Time.Current + 2000 }))); + AddStep("Add hit object", () => scrollingHitObjectContainer.Add(new TestDrawableHitObject(new HitObject { StartTime = Time.Current + 2000 }))); } protected override void Update() @@ -151,7 +154,7 @@ namespace osu.Desktop.Tests.Visual } } - private class TestDrawableHitObject : DrawableHitObject, IScrollingHitObject + private class TestDrawableHitObject : DrawableHitObject, IScrollingHitObject { private readonly Box background; private const float height = 14; @@ -204,6 +207,18 @@ namespace osu.Desktop.Tests.Visual if (Time.Current >= HitObject.StartTime) background.Colour = Color4.Red; } + + protected override Judgement CreateJudgement() => new TestJudgement(); + + protected override void UpdateState(ArmedState state) + { + } + + private class TestJudgement : Judgement + { + public override string ResultString { get { throw new NotImplementedException(); } } + public override string MaxResultString { get { throw new NotImplementedException(); } } + } } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 94cf6a4337..424ed77783 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -15,10 +15,15 @@ using osu.Game.Rulesets.Objects.Drawables; using System; using osu.Framework.Configuration; using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Judgements; +using osu.Framework.Allocation; namespace osu.Game.Rulesets.Mania.UI { - public class Column : Container, IHasAccentColour + public class Column : ScrollingPlayfield, IHasAccentColour { private const float key_icon_size = 10; private const float key_icon_corner_radius = 3; @@ -30,13 +35,6 @@ namespace osu.Game.Rulesets.Mania.UI private const float column_width = 45; private const float special_column_width = 70; - private readonly BindableDouble visibleTimeRange = new BindableDouble(); - public BindableDouble VisibleTimeRange - { - get { return visibleTimeRange; } - set { visibleTimeRange.BindTo(value); } - } - /// /// The key that will trigger input actions for this column and hit objects contained inside it. /// @@ -46,14 +44,15 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Container hitTargetBar; private readonly Container keyIcon; - private readonly SpeedAdjustmentCollection speedAdjustments; + protected override Container Content => content; + private readonly Container content; public Column() + : base(Axes.Y) { - RelativeSizeAxes = Axes.Y; Width = column_width; - Children = new Drawable[] + InternalChildren = new Drawable[] { background = new Box { @@ -97,11 +96,10 @@ namespace osu.Game.Rulesets.Mania.UI } } }, - speedAdjustments = new SpeedAdjustmentCollection(Axes.Y) + content = new Container { Name = "Hit objects", RelativeSizeAxes = Axes.Both, - VisibleTimeRange = VisibleTimeRange }, // For column lighting, we need to capture input events before the notes new InputTarget @@ -150,6 +148,8 @@ namespace osu.Game.Rulesets.Mania.UI }; } + public override Axes RelativeSizeAxes => Axes.Y; + private bool isSpecial; public bool IsSpecial { @@ -192,11 +192,16 @@ namespace osu.Game.Rulesets.Mania.UI } } - public void Add(SpeedAdjustmentContainer speedAdjustment) => speedAdjustments.Add(speedAdjustment); - public void Add(DrawableHitObject hitObject) + public void Add(SpeedAdjustmentContainer speedAdjustment) => HitObjects.AddSpeedAdjustment(speedAdjustment); + + /// + /// Adds a DrawableHitObject to this Playfield. + /// + /// The DrawableHitObject to add. + public override void Add(DrawableHitObject hitObject) { hitObject.AccentColour = AccentColour; - speedAdjustments.Add(hitObject); + HitObjects.Add(hitObject); } private bool onKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs index 2805c129f3..04185f0872 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaHitRenderer.cs @@ -39,24 +39,9 @@ namespace osu.Game.Rulesets.Mania.UI public IEnumerable BarLines; - /// - /// Per-column timing changes. - /// - private readonly List[] hitObjectSpeedAdjustments; - - /// - /// Bar line timing changes. - /// - private readonly List barLineSpeedAdjustments = new List(); - public ManiaHitRenderer(WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(beatmap, isForCurrentRuleset) { - // Generate the speed adjustment container lists - hitObjectSpeedAdjustments = new List[PreferredColumns]; - for (int i = 0; i < PreferredColumns; i++) - hitObjectSpeedAdjustments[i] = new List(); - // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; @@ -83,30 +68,12 @@ namespace osu.Game.Rulesets.Mania.UI } BarLines = barLines; - - // Generate speed adjustments from mods first - bool useDefaultSpeedAdjustments = true; - - if (Mods != null) - { - foreach (var speedAdjustmentMod in Mods.OfType()) - { - useDefaultSpeedAdjustments = false; - speedAdjustmentMod.ApplyToHitRenderer(this, ref hitObjectSpeedAdjustments, ref barLineSpeedAdjustments); - } - } - - // Generate the default speed adjustments - if (useDefaultSpeedAdjustments) - generateDefaultSpeedAdjustments(); } [BackgroundDependencyLoader] private void load() { - var maniaPlayfield = (ManiaPlayfield)Playfield; - - BarLines.ForEach(maniaPlayfield.Add); + BarLines.ForEach(Playfield.Add); } protected override void ApplyBeatmap() @@ -116,28 +83,6 @@ namespace osu.Game.Rulesets.Mania.UI PreferredColumns = (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize)); } - protected override void ApplySpeedAdjustments() - { - var maniaPlayfield = (ManiaPlayfield)Playfield; - - for (int i = 0; i < PreferredColumns; i++) - foreach (var change in hitObjectSpeedAdjustments[i]) - maniaPlayfield.Columns.ElementAt(i).Add(change); - - foreach (var change in barLineSpeedAdjustments) - maniaPlayfield.Add(change); - } - - private void generateDefaultSpeedAdjustments() - { - DefaultControlPoints.ForEach(c => - { - foreach (List t in hitObjectSpeedAdjustments) - t.Add(new ManiaSpeedAdjustmentContainer(c, ScrollingAlgorithm.Basic)); - barLineSpeedAdjustments.Add(new ManiaSpeedAdjustmentContainer(c, ScrollingAlgorithm.Basic)); - }); - } - protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(PreferredColumns) { Anchor = Anchor.Centre, @@ -168,5 +113,13 @@ namespace osu.Game.Rulesets.Mania.UI } protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f); + + protected override void ApplySpeedAdjustment(MultiplierControlPoint controlPoint) + { + base.ApplySpeedAdjustment(controlPoint); + Playfield.Columns.ForEach(c => c.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint))); + } + + protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index b81b9c44fe..3b1dc58e4f 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -20,18 +20,14 @@ using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Timing; using osu.Framework.Configuration; using osu.Framework.Graphics.Shapes; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaPlayfield : SpeedAdjustedPlayfield + public class ManiaPlayfield : ScrollingPlayfield { public const float HIT_TARGET_POSITION = 50; - private const double time_span_default = 1500; - private const double time_span_min = 50; - private const double time_span_max = 10000; - private const double time_span_step = 50; - /// /// Default column keys, expanding outwards from the middle as more column are added. /// E.g. 2 columns use FJ, 4 columns use DFJK, 6 use SDFJKL, etc... @@ -56,13 +52,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly FlowContainer columns; public IEnumerable Columns => columns.Children; - private readonly BindableDouble visibleTimeRange = new BindableDouble(time_span_default) - { - MinValue = time_span_min, - MaxValue = time_span_max - }; - - private readonly SpeedAdjustmentCollection barLineContainer; + private readonly ScrollingHitObjectContainer barLineContainer; private List normalColumnColours = new List(); private Color4 specialColumnColour; @@ -70,6 +60,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly int columnCount; public ManiaPlayfield(int columnCount) + : base(Axes.Y) { this.columnCount = columnCount; @@ -120,13 +111,13 @@ namespace osu.Game.Rulesets.Mania.UI Padding = new MarginPadding { Top = HIT_TARGET_POSITION }, Children = new[] { - barLineContainer = new SpeedAdjustmentCollection(Axes.Y) + barLineContainer = new ScrollingHitObjectContainer(Axes.Y) { Name = "Bar lines", Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Y, - VisibleTimeRange = visibleTimeRange + VisibleTimeRange = VisibleTimeRange // Width is set in the Update method } } @@ -136,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.UI }; for (int i = 0; i < columnCount; i++) - columns.Add(new Column { VisibleTimeRange = visibleTimeRange }); + columns.Add(new Column { VisibleTimeRange = VisibleTimeRange }); } [BackgroundDependencyLoader] @@ -211,30 +202,7 @@ namespace osu.Game.Rulesets.Mania.UI public override void Add(DrawableHitObject h) => Columns.ElementAt(h.HitObject.Column).Add(h); public void Add(DrawableBarLine barline) => barLineContainer.Add(barline); - public void Add(SpeedAdjustmentContainer speedAdjustment) => barLineContainer.Add(speedAdjustment); - - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) - { - if (state.Keyboard.ControlPressed) - { - switch (args.Key) - { - case Key.Minus: - transformVisibleTimeRangeTo(visibleTimeRange + time_span_step, 200, Easing.OutQuint); - break; - case Key.Plus: - transformVisibleTimeRangeTo(visibleTimeRange - time_span_step, 200, Easing.OutQuint); - break; - } - } - - return false; - } - - private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None) - { - this.TransformTo(nameof(visibleTimeRange), newTimeRange, duration, easing); - } + public void Add(SpeedAdjustmentContainer speedAdjustment) => barLineContainer.AddSpeedAdjustment(speedAdjustment); protected override void Update() { diff --git a/osu.Game/Rulesets/Timing/ScrollingContainer.cs b/osu.Game/Rulesets/Timing/ScrollingContainer.cs index c412530b15..daeca5a910 100644 --- a/osu.Game/Rulesets/Timing/ScrollingContainer.cs +++ b/osu.Game/Rulesets/Timing/ScrollingContainer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Timing /// public abstract class ScrollingContainer : Container { - private readonly BindableDouble visibleTimeRange = new BindableDouble(); + private readonly BindableDouble visibleTimeRange = new BindableDouble { Default = 1000 }; /// /// Gets or sets the range of time that is visible by the length of this container. /// diff --git a/osu.Game/Rulesets/Timing/SpeedAdjustmentCollection.cs b/osu.Game/Rulesets/Timing/SpeedAdjustmentCollection.cs deleted file mode 100644 index 22213be740..0000000000 --- a/osu.Game/Rulesets/Timing/SpeedAdjustmentCollection.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Timing -{ - /// - /// A collection of s. - /// - /// - /// This container redirects any 's added to it to the - /// which provides the speed adjustment active at the start time of the hit object. Furthermore, this container provides the - /// necessary for the contained s. - /// - /// - public class SpeedAdjustmentCollection : Container - { - private readonly BindableDouble visibleTimeRange = new BindableDouble(); - /// - /// Gets or sets the range of time that is visible by the length of this container. - /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. - /// - public Bindable VisibleTimeRange - { - get { return visibleTimeRange; } - set { visibleTimeRange.BindTo(value); } - } - - protected override int Compare(Drawable x, Drawable y) - { - var xSpeedAdjust = x as SpeedAdjustmentContainer; - var ySpeedAdjust = y as SpeedAdjustmentContainer; - - // If either of the two drawables are not hit objects, fall back to the base comparer - if (xSpeedAdjust?.ControlPoint == null || ySpeedAdjust?.ControlPoint == null) - return CompareReverseChildID(x, y); - - // Compare by start time - int i = ySpeedAdjust.ControlPoint.StartTime.CompareTo(xSpeedAdjust.ControlPoint.StartTime); - - return i != 0 ? i : CompareReverseChildID(x, y); - } - - /// - /// Hit objects that are to be re-processed on the next update. - /// - private readonly Queue queuedHitObjects = new Queue(); - - private readonly Axes scrollingAxes; - - /// - /// Creates a new . - /// - /// The axes upon which hit objects should appear to scroll inside this container. - public SpeedAdjustmentCollection(Axes scrollingAxes) - { - this.scrollingAxes = scrollingAxes; - } - - public override void Add(SpeedAdjustmentContainer speedAdjustment) - { - speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange); - speedAdjustment.ScrollingAxes = scrollingAxes; - base.Add(speedAdjustment); - } - - /// - /// Adds a hit object to this . The hit objects will be kept in a queue - /// and will be processed when new s are added to this . - /// - /// The hit object to add. - public void Add(DrawableHitObject hitObject) - { - if (!(hitObject is IScrollingHitObject)) - throw new InvalidOperationException($"Hit objects added to a {nameof(SpeedAdjustmentCollection)} must implement {nameof(IScrollingHitObject)}."); - - queuedHitObjects.Enqueue(hitObject); - } - - protected override void Update() - { - base.Update(); - - // Todo: At the moment this is going to re-process every single Update, however this will only be a null-op - // when there are no SpeedAdjustmentContainers available. This should probably error or something, but it's okay for now. - - // An external count is kept because hit objects that can't be added are re-queued - int count = queuedHitObjects.Count; - while (count-- > 0) - { - var hitObject = queuedHitObjects.Dequeue(); - - var target = adjustmentContainerFor(hitObject); - if (target == null) - { - // We can't add this hit object to a speed adjustment container yet, so re-queue it - // for re-processing when the layout next invalidated - queuedHitObjects.Enqueue(hitObject); - continue; - } - - if (hitObject.RelativePositionAxes != target.ScrollingAxes) - throw new InvalidOperationException($"Make sure to set all {nameof(DrawableHitObject)}'s {nameof(RelativePositionAxes)} are equal to the correct axes of scrolling ({target.ScrollingAxes})."); - - target.Add(hitObject); - } - } - - /// - /// Finds the which provides the speed adjustment active at the start time - /// of a hit object. If there is no active at the start time of the hit object, - /// then the first (time-wise) speed adjustment is returned. - /// - /// The hit object to find the active for. - /// The active at 's start time. Null if there are no speed adjustments. - private SpeedAdjustmentContainer adjustmentContainerFor(DrawableHitObject hitObject) => Children.FirstOrDefault(c => c.CanContain(hitObject)) ?? Children.LastOrDefault(); - - /// - /// Finds the which provides the speed adjustment active at a time. - /// If there is no active at the time, then the first (time-wise) speed adjustment is returned. - /// - /// The time to find the active at. - /// The active at . Null if there are no speed adjustments. - private SpeedAdjustmentContainer adjustmentContainerAt(double time) => Children.FirstOrDefault(c => c.CanContain(time)) ?? Children.LastOrDefault(); - } -} diff --git a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs index 3c0ba86db3..6be721f6e7 100644 --- a/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs +++ b/osu.Game/Rulesets/Timing/SpeedAdjustmentContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Timing /// public class SpeedAdjustmentContainer : Container { - private readonly Bindable visibleTimeRange = new Bindable(); + private readonly Bindable visibleTimeRange = new Bindable { Default = 1000 }; /// /// Gets or sets the range of time that is visible by the length of this container. /// diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index ff321a18a5..5f07df5088 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.UI /// /// The HitObjects contained in this Playfield. /// - protected HitObjectContainer> HitObjects; + public HitObjectContainer> HitObjects { get; protected set; } internal Container ScaledContent; @@ -99,7 +99,8 @@ namespace osu.Game.Rulesets.UI protected override Vector2 DrawScale => CustomWidth.HasValue ? new Vector2(DrawSize.X / CustomWidth.Value) : base.DrawScale; } - public class HitObjectContainer : Container where U : Drawable + public class HitObjectContainer : Container + where U : Drawable { } } diff --git a/osu.Game/Rulesets/UI/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/ScrollingPlayfield.cs new file mode 100644 index 0000000000..b765b533a0 --- /dev/null +++ b/osu.Game/Rulesets/UI/ScrollingPlayfield.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using OpenTK.Input; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Timing; + +namespace osu.Game.Rulesets.UI +{ + public class ScrollingPlayfield : Playfield + where TObject : HitObject + where TJudgement : Judgement + { + private const double time_span_default = 1500; + private const double time_span_min = 50; + private const double time_span_max = 10000; + private const double time_span_step = 50; + + /// + /// Gets or sets the range of time that is visible by the length of this playfield the scrolling axis direction. + /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. + /// + private readonly BindableDouble visibleTimeRange = new BindableDouble(time_span_default) + { + Default = time_span_default, + MinValue = time_span_min, + MaxValue = time_span_max + }; + + public BindableDouble VisibleTimeRange + { + get { return visibleTimeRange; } + set { visibleTimeRange.BindTo(value); } + } + + public new readonly ScrollingHitObjectContainer HitObjects; + + protected ScrollingPlayfield(Axes scrollingAxes, float? customWidth = null) + : base(customWidth) + { + base.HitObjects = HitObjects = new ScrollingHitObjectContainer(scrollingAxes) + { + RelativeSizeAxes = Axes.Both, + VisibleTimeRange = VisibleTimeRange + }; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (state.Keyboard.ControlPressed) + { + switch (args.Key) + { + case Key.Minus: + transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint); + break; + case Key.Plus: + transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint); + break; + } + } + + return false; + } + + private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None) + { + this.TransformTo(nameof(VisibleTimeRange), newTimeRange, duration, easing); + } + + /// + /// A collection of s. + /// + /// + /// This container redirects any 's added to it to the + /// which provides the speed adjustment active at the start time of the hit object. Furthermore, this container provides the + /// necessary for the contained s. + /// + /// + public class ScrollingHitObjectContainer : HitObjectContainer> + { + private readonly BindableDouble visibleTimeRange = new BindableDouble { Default = 1000 }; + /// + /// Gets or sets the range of time that is visible by the length of this container. + /// For example, only hit objects with start time less than or equal to 1000 will be visible with = 1000. + /// + public Bindable VisibleTimeRange + { + get { return visibleTimeRange; } + set { visibleTimeRange.BindTo(value); } + } + + protected override Container> Content => content; + /// + /// The following is never used - it only exists for the purpose of being able to use AddInternal below. + /// + private Container> content; + + /// + /// Hit objects that are to be re-processed on the next update. + /// + private readonly Queue> queuedHitObjects = new Queue>(); + + private readonly Axes scrollingAxes; + + /// + /// Creates a new . + /// + /// The axes upon which hit objects should appear to scroll inside this container. + public ScrollingHitObjectContainer(Axes scrollingAxes) + { + this.scrollingAxes = scrollingAxes; + + content = new Container>(); + } + + public void AddSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment) + { + speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange); + speedAdjustment.ScrollingAxes = scrollingAxes; + AddInternal(speedAdjustment); + } + + /// + /// Adds a hit object to this . The hit objects will be kept in a queue + /// and will be processed when new s are added to this . + /// + /// The hit object to add. + public override void Add(DrawableHitObject hitObject) + { + if (!(hitObject is IScrollingHitObject)) + throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}."); + + queuedHitObjects.Enqueue(hitObject); + } + + protected override void Update() + { + base.Update(); + + // Todo: At the moment this is going to re-process every single Update, however this will only be a null-op + // when there are no SpeedAdjustmentContainers available. This should probably error or something, but it's okay for now. + + // An external count is kept because hit objects that can't be added are re-queued + int count = queuedHitObjects.Count; + while (count-- > 0) + { + var hitObject = queuedHitObjects.Dequeue(); + + var target = adjustmentContainerFor(hitObject); + if (target == null) + { + // We can't add this hit object to a speed adjustment container yet, so re-queue it + // for re-processing when the layout next invalidated + queuedHitObjects.Enqueue(hitObject); + continue; + } + + if (hitObject.RelativePositionAxes != target.ScrollingAxes) + throw new InvalidOperationException($"Make sure to set all {nameof(DrawableHitObject)}'s {nameof(RelativePositionAxes)} are equal to the correct axes of scrolling ({target.ScrollingAxes})."); + + target.Add(hitObject); + } + } + + /// + /// Finds the which provides the speed adjustment active at the start time + /// of a hit object. If there is no active at the start time of the hit object, + /// then the first (time-wise) speed adjustment is returned. + /// + /// The hit object to find the active for. + /// The active at 's start time. Null if there are no speed adjustments. + private SpeedAdjustmentContainer adjustmentContainerFor(DrawableHitObject hitObject) => InternalChildren.OfType().FirstOrDefault(c => c.CanContain(hitObject)) ?? InternalChildren.OfType().LastOrDefault(); + + /// + /// Finds the which provides the speed adjustment active at a time. + /// If there is no active at the time, then the first (time-wise) speed adjustment is returned. + /// + /// The time to find the active at. + /// The active at . Null if there are no speed adjustments. + private SpeedAdjustmentContainer adjustmentContainerAt(double time) => InternalChildren.OfType().FirstOrDefault(c => c.CanContain(time)) ?? InternalChildren.OfType().LastOrDefault(); + } + } +} \ No newline at end of file diff --git a/osu.Game/Rulesets/UI/SpeedAdjustedHitRenderer.cs b/osu.Game/Rulesets/UI/SpeedAdjustedHitRenderer.cs index 2dadbb04d0..5d6293c802 100644 --- a/osu.Game/Rulesets/UI/SpeedAdjustedHitRenderer.cs +++ b/osu.Game/Rulesets/UI/SpeedAdjustedHitRenderer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Lists; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.UI public abstract class SpeedAdjustedHitRenderer : HitRenderer where TObject : HitObject where TJudgement : Judgement - where TPlayfield : SpeedAdjustedPlayfield + where TPlayfield : ScrollingPlayfield { protected readonly SortedList DefaultControlPoints = new SortedList(Comparer.Default); @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.UI [BackgroundDependencyLoader] private void load() { - ApplySpeedAdjustments(); + DefaultControlPoints.ForEach(c => ApplySpeedAdjustment(c)); } protected override void ApplyBeatmap() @@ -98,9 +99,11 @@ namespace osu.Game.Rulesets.UI return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone()); } - /// - /// Applies speed changes to the playfield. - /// - protected abstract void ApplySpeedAdjustments(); + protected virtual void ApplySpeedAdjustment(MultiplierControlPoint controlPoint) + { + Playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint)); + } + + protected abstract SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint); } } diff --git a/osu.Game/Rulesets/UI/SpeedAdjustedPlayfield.cs b/osu.Game/Rulesets/UI/SpeedAdjustedPlayfield.cs deleted file mode 100644 index 1d1f890a6e..0000000000 --- a/osu.Game/Rulesets/UI/SpeedAdjustedPlayfield.cs +++ /dev/null @@ -1,15 +0,0 @@ -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Rulesets.UI -{ - public class SpeedAdjustedPlayfield : Playfield - where TObject : HitObject - where TJudgement : Judgement - { - protected SpeedAdjustedPlayfield(float? customWidth = null) - : base(customWidth) - { - } - } -} \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 075f0732f7..342a02ba60 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -230,7 +230,6 @@ - @@ -341,7 +340,7 @@ - +