From 117ab8a26d32c5277cb1c585c73fdc726d0f1d38 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 7 Jan 2018 12:47:09 +0900 Subject: [PATCH] Split out scrolling algorithm --- .../Visual/TestCaseScrollingHitObjects.cs | 6 +- osu.Game/Rulesets/UI/HitObjectContainer.cs | 19 +++ osu.Game/Rulesets/UI/Playfield.cs | 22 ++-- .../Algorithms/GlobalScrollingAlgorithm.cs | 98 +++++++++++++++ .../Algorithms/IScrollingAlgorithm.cs | 15 +++ .../GlobalScrollingHitObjectContainer.cs | 17 +++ .../Scrolling/ScrollingHitObjectContainer.cs | 115 +++++------------- .../UI/Scrolling/ScrollingPlayfield.cs | 16 ++- osu.Game/osu.Game.csproj | 4 + 9 files changed, 205 insertions(+), 107 deletions(-) create mode 100644 osu.Game/Rulesets/UI/HitObjectContainer.cs create mode 100644 osu.Game/Rulesets/UI/Scrolling/Algorithms/GlobalScrollingAlgorithm.cs create mode 100644 osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollingAlgorithm.cs create mode 100644 osu.Game/Rulesets/UI/Scrolling/GlobalScrollingHitObjectContainer.cs diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs index 591bf4fadd..4f2e895e9d 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs @@ -99,19 +99,17 @@ namespace osu.Game.Tests.Visual - private class TestPlayfield : Playfield + private class TestPlayfield : ScrollingPlayfield { public readonly BindableDouble TimeRange = new BindableDouble(5000); public readonly ScrollingDirection Direction; - public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects; - public TestPlayfield(ScrollingDirection direction) + : base(direction) { Direction = direction; - base.HitObjects = new ScrollingHitObjectContainer(direction); HitObjects.TimeRange.BindTo(TimeRange); } } diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs new file mode 100644 index 0000000000..e7843c86ca --- /dev/null +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.UI +{ + public class HitObjectContainer : CompositeDrawable + { + public virtual IEnumerable Objects => InternalChildren.Cast(); + public virtual IEnumerable AliveObjects => AliveInternalChildren.Cast(); + + public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); + public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 61014b5550..91ea3ade2a 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -8,8 +8,6 @@ using osu.Game.Rulesets.Objects.Drawables; using OpenTK; using osu.Game.Rulesets.Judgements; using osu.Framework.Allocation; -using System.Collections.Generic; -using System.Linq; namespace osu.Game.Rulesets.UI { @@ -18,7 +16,7 @@ namespace osu.Game.Rulesets.UI /// /// The HitObjects contained in this Playfield. /// - public HitObjectContainer HitObjects { get; protected set; } + public readonly HitObjectContainer HitObjects; public Container ScaledContent; @@ -52,10 +50,8 @@ namespace osu.Game.Rulesets.UI } }); - HitObjects = new HitObjectContainer - { - RelativeSizeAxes = Axes.Both, - }; + HitObjects = CreateHitObjectContainer(); + HitObjects.RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -94,14 +90,10 @@ namespace osu.Game.Rulesets.UI /// The that occurred. public virtual void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { } - public class HitObjectContainer : CompositeDrawable - { - public virtual IEnumerable Objects => InternalChildren.Cast(); - public virtual IEnumerable AliveObjects => AliveInternalChildren.Cast(); - - public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject); - public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject); - } + /// + /// Creates the container that will be used to contain the s. + /// + protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); private class ScaledContainer : Container { diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/GlobalScrollingAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/GlobalScrollingAlgorithm.cs new file mode 100644 index 0000000000..0a69d00377 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/GlobalScrollingAlgorithm.cs @@ -0,0 +1,98 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Timing; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Algorithms +{ + public class GlobalScrollingAlgorithm : IScrollingAlgorithm + { + private readonly Dictionary hitObjectPositions = new Dictionary(); + + private readonly IReadOnlyList controlPoints; + + public GlobalScrollingAlgorithm(IReadOnlyList controlPoints) + { + this.controlPoints = controlPoints; + } + + public void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length) + { + foreach (var obj in hitObjects) + { + var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime, timeRange); + + obj.LifetimeStart = obj.HitObject.StartTime - timeRange - 1000; + obj.LifetimeEnd = ((obj.HitObject as IHasEndTime)?.EndTime ?? obj.HitObject.StartTime) + timeRange + 1000; + + if (!(obj.HitObject is IHasEndTime endTime)) + continue; + + var diff = positionAt(endTime.EndTime, timeRange) - startPosition; + + switch (direction) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + obj.Height = (float)(diff * length.Y); + break; + case ScrollingDirection.Left: + case ScrollingDirection.Right: + obj.Width = (float)(diff * length.X); + break; + } + } + } + + public void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length) + { + var timelinePosition = positionAt(currentTime, timeRange); + + foreach (var obj in hitObjects) + { + var finalPosition = hitObjectPositions[obj] - timelinePosition; + + switch (direction) + { + case ScrollingDirection.Up: + obj.Y = (float)(finalPosition * length.Y); + break; + case ScrollingDirection.Down: + obj.Y = (float)(-finalPosition * length.Y); + break; + case ScrollingDirection.Left: + obj.X = (float)(finalPosition * length.X); + break; + case ScrollingDirection.Right: + obj.X = (float)(-finalPosition * length.X); + break; + } + } + } + + private double positionAt(double time, double timeRange) + { + double length = 0; + for (int i = 0; i < controlPoints.Count; i++) + { + var current = controlPoints[i]; + var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + + if (i > 0 && current.StartTime > time) + continue; + + // Duration of the current control point + var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; + + length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / timeRange; + } + + return length; + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollingAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollingAlgorithm.cs new file mode 100644 index 0000000000..2621ae7d6f --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollingAlgorithm.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.UI.Scrolling.Algorithms +{ + public interface IScrollingAlgorithm + { + void ComputeInitialStates(IEnumerable hitObjects, ScrollingDirection direction, double timeRange, Vector2 length); + void ComputePositions(IEnumerable hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length); + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/GlobalScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/GlobalScrollingHitObjectContainer.cs new file mode 100644 index 0000000000..23dff01940 --- /dev/null +++ b/osu.Game/Rulesets/UI/Scrolling/GlobalScrollingHitObjectContainer.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using osu.Game.Rulesets.UI.Scrolling.Algorithms; + +namespace osu.Game.Rulesets.UI.Scrolling +{ + public class GlobalScrollingHitObjectContainer : ScrollingHitObjectContainer + { + public GlobalScrollingHitObjectContainer(ScrollingDirection direction) + : base(direction) + { + } + + protected override IScrollingAlgorithm CreateScrollingAlgorithm() => new GlobalScrollingAlgorithm(ControlPoints); + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index e0df4321a8..e1d8fe8d59 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -1,19 +1,17 @@ // 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 osu.Framework.Caching; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Lists; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { - public class ScrollingHitObjectContainer : Playfield.HitObjectContainer + public abstract class ScrollingHitObjectContainer : HitObjectContainer { public readonly BindableDouble TimeRange = new BindableDouble { @@ -21,22 +19,32 @@ namespace osu.Game.Rulesets.UI.Scrolling MaxValue = double.MaxValue }; + protected readonly SortedList ControlPoints = new SortedList(); + private readonly ScrollingDirection direction; - private Cached positionCache = new Cached(); + private Cached initialStateCache = new Cached(); - public ScrollingHitObjectContainer(ScrollingDirection direction) + protected ScrollingHitObjectContainer(ScrollingDirection direction) { this.direction = direction; RelativeSizeAxes = Axes.Both; - TimeRange.ValueChanged += v => positionCache.Invalidate(); + TimeRange.ValueChanged += v => initialStateCache.Invalidate(); + } + + private IScrollingAlgorithm scrollingAlgorithm; + protected override void LoadComplete() + { + base.LoadComplete(); + + scrollingAlgorithm = CreateScrollingAlgorithm(); } public override void Add(DrawableHitObject hitObject) { - positionCache.Invalidate(); + initialStateCache.Invalidate(); base.Add(hitObject); } @@ -44,69 +52,42 @@ namespace osu.Game.Rulesets.UI.Scrolling { var result = base.Remove(hitObject); if (result) - positionCache.Invalidate(); + initialStateCache.Invalidate(); return result; } - private readonly SortedList controlPoints = new SortedList(); - public void AddControlPoint(MultiplierControlPoint controlPoint) { - controlPoints.Add(controlPoint); - positionCache.Invalidate(); + ControlPoints.Add(controlPoint); + initialStateCache.Invalidate(); } public bool RemoveControlPoint(MultiplierControlPoint controlPoint) { - var result = controlPoints.Remove(controlPoint); + var result = ControlPoints.Remove(controlPoint); if (result) - positionCache.Invalidate(); + initialStateCache.Invalidate(); return result; } public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { if ((invalidation & (Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo)) > 0) - positionCache.Invalidate(); + initialStateCache.Invalidate(); return base.Invalidate(invalidation, source, shallPropagate); } - private readonly Dictionary hitObjectPositions = new Dictionary(); - protected override void Update() { base.Update(); - if (positionCache.IsValid) + if (initialStateCache.IsValid) return; - foreach (var obj in Objects) - { - var startPosition = hitObjectPositions[obj] = positionAt(obj.HitObject.StartTime); + scrollingAlgorithm.ComputeInitialStates(Objects, direction, TimeRange, DrawSize); - obj.LifetimeStart = obj.HitObject.StartTime - TimeRange - 1000; - obj.LifetimeEnd = ((obj.HitObject as IHasEndTime)?.EndTime ?? obj.HitObject.StartTime) + TimeRange + 1000; - - if (!(obj.HitObject is IHasEndTime endTime)) - continue; - - var length = positionAt(endTime.EndTime) - startPosition; - - switch (direction) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - obj.Height = (float)(length * DrawHeight); - break; - case ScrollingDirection.Left: - case ScrollingDirection.Right: - obj.Width = (float)(length * DrawWidth); - break; - } - } - - positionCache.Validate(); + initialStateCache.Validate(); } protected override void UpdateAfterChildrenLife() @@ -116,48 +97,12 @@ namespace osu.Game.Rulesets.UI.Scrolling // We need to calculate this as soon as possible after lifetimes so that hitobjects // get the final say in their positions - var timelinePosition = positionAt(Time.Current); - - foreach (var obj in AliveObjects) - { - var finalPosition = hitObjectPositions[obj] - timelinePosition; - - switch (direction) - { - case ScrollingDirection.Up: - obj.Y = (float)(finalPosition * DrawHeight); - break; - case ScrollingDirection.Down: - obj.Y = (float)(-finalPosition * DrawHeight); - break; - case ScrollingDirection.Left: - obj.X = (float)(finalPosition * DrawWidth); - break; - case ScrollingDirection.Right: - obj.X = (float)(-finalPosition * DrawWidth); - break; - } - } + scrollingAlgorithm.ComputePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize); } - private double positionAt(double time) - { - double length = 0; - for (int i = 0; i < controlPoints.Count; i++) - { - var current = controlPoints[i]; - var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; - - if (i > 0 && current.StartTime > time) - continue; - - // Duration of the current control point - var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; - - length += Math.Min(currentDuration, time - current.StartTime) * current.Multiplier / TimeRange; - } - - return length; - } + /// + /// Creates the algorithm that will process the positions of the s. + /// + protected abstract IScrollingAlgorithm CreateScrollingAlgorithm(); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 899048531f..f560d8eaa6 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// A type of specialized towards scrolling s. /// - public class ScrollingPlayfield : Playfield + public abstract class ScrollingPlayfield : Playfield { /// /// The default span of time visible by the length of the scrolling axes. @@ -49,7 +49,9 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// The container that contains the s and s. /// - public new readonly ScrollingHitObjectContainer HitObjects; + public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects; + + private readonly ScrollingDirection direction; /// /// Creates a new . @@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.UI.Scrolling protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null) : base(customWidth) { - base.HitObjects = HitObjects = new ScrollingHitObjectContainer(direction) { RelativeSizeAxes = Axes.Both }; + this.direction = direction; HitObjects.TimeRange.BindTo(VisibleTimeRange); } @@ -105,6 +107,14 @@ namespace osu.Game.Rulesets.UI.Scrolling this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing)); } + protected sealed override HitObjectContainer CreateHitObjectContainer() => CreateScrollingHitObjectContainer(); + + /// + /// Creates the that will handle the scrolling of the s. + /// + /// + protected virtual ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new GlobalScrollingHitObjectContainer(direction); + private class TransformVisibleTimeRange : Transform { private double valueAt(double time) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6d8ce3c1b6..a33cf73934 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -315,6 +315,10 @@ + + + +