diff --git a/osu.Android.props b/osu.Android.props
index 702591231b..25bde037db 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -54,6 +54,6 @@
-
+
diff --git a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
index d7f709dc03..a6e8622b6f 100644
--- a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
@@ -18,12 +18,21 @@ namespace osu.Game.Tests.ScrollAlgorithms
}
[Test]
- public void TestDisplayStartTime()
+ public void TestPointDisplayStartTime()
{
- Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 10000));
- Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 5000));
- Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 5000));
- Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 10000));
+ Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 0, 10000, 1));
+ Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 0, 5000, 1));
+ Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 0, 5000, 1));
+ Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 0, 10000, 1));
+ }
+
+ [Test]
+ public void TestObjectDisplayStartTime()
+ {
+ Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000
+ Assert.AreEqual(8900, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000
+ Assert.AreEqual(13500, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000
+ Assert.AreEqual(19000, algorithm.GetDisplayStartTime(25000, 100, 5000, 500)); // 25000 - (1 + 100 / 500) * 5000
}
[Test]
diff --git a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
index 106aa88be3..1429d22c1a 100644
--- a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
@@ -27,11 +27,22 @@ namespace osu.Game.Tests.ScrollAlgorithms
}
[Test]
- public void TestDisplayStartTime()
+ public void TestPointDisplayStartTime()
{
- Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 1000)); // Like constant
- Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 1000)); // 10500 - (1000 * 0.5)
- Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 1000)); // 23000 - (1000 / 0.5)
+ Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 0, 1000, 1)); // Like constant
+ Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 0, 1000, 1)); // 10500 - (1000 * 0.5)
+ Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 0, 1000, 1)); // 23000 - (1000 / 0.5)
+ }
+
+ [Test]
+ public void TestObjectDisplayStartTime()
+ {
+ Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000 / 1
+ Assert.AreEqual(9450, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000 / 2
+ Assert.AreEqual(14250, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000 / 2
+ Assert.AreEqual(16500, algorithm.GetDisplayStartTime(18000, 250, 2000, 500)); // 18000 - (1 + 250 / 500) * 2000 / 2
+ Assert.AreEqual(17800, algorithm.GetDisplayStartTime(20000, 50, 1000, 500)); // 20000 - (1 + 50 / 500) * 1000 / 0.5
+ Assert.AreEqual(19800, algorithm.GetDisplayStartTime(22000, 50, 1000, 500)); // 22000 - (1 + 50 / 500) * 1000 / 0.5
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index 8629522dc2..d03716db2e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
+using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -19,6 +20,7 @@ using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
@@ -30,7 +32,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
- private const int spawn_interval = 5000;
+ private const int time_range = 5000;
+ private const int spawn_rate = time_range / 10;
private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4];
private readonly TestPlayfield[] playfields = new TestPlayfield[4];
@@ -50,13 +53,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{
RelativeSizeAxes = Axes.Both,
Child = playfields[0] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
},
scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Down)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[1] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
},
},
new Drawable[]
@@ -65,13 +68,13 @@ namespace osu.Game.Tests.Visual.Gameplay
{
RelativeSizeAxes = Axes.Both,
Child = playfields[2] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
},
scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Right)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[3] = new TestPlayfield(),
- TimeRange = spawn_interval
+ TimeRange = time_range
}
}
}
@@ -84,31 +87,55 @@ namespace osu.Game.Tests.Visual.Gameplay
{
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
- for (int i = 0; i <= spawn_interval; i += 1000)
+ for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
addHitObject(Time.Current + i);
hitObjectSpawnDelegate?.Cancel();
- hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + spawn_interval), 1000, true);
+ hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
}
+ private IList testControlPoints => new List
+ {
+ new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } },
+ new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } },
+ new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } }
+ };
+
[Test]
public void TestScrollAlgorithms()
{
- AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
- AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
- AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
+ AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
+ AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
+ AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
- AddSliderStep("Time range", 100, 10000, spawn_interval, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
- AddStep("Add control point", () => addControlPoint(Time.Current + spawn_interval));
+ AddSliderStep("time range", 100, 10000, time_range, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
+
+ AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
[Test]
- public void TestScrollLifetime()
+ public void TestConstantScrollLifetime()
{
- AddStep("Set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
+ AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
// scroll container time range must be less than the rate of spawning hitobjects
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
- AddStep("Set time range", () => scrollContainers.ForEach(c => c.TimeRange = spawn_interval / 2.0));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
+ }
+
+ [Test]
+ public void TestSequentialScrollLifetime()
+ {
+ AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
+ AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
+ }
+
+ [Test]
+ public void TestOverlappingScrollLifetime()
+ {
+ AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
+ AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
private void addHitObject(double time)
@@ -122,28 +149,27 @@ namespace osu.Game.Tests.Visual.Gameplay
});
}
- private void addControlPoint(double time)
+ private TestDrawableControlPoint createDrawablePoint(TestPlayfield playfield, double t)
{
- scrollContainers.ForEach(c =>
+ var obj = new TestDrawableControlPoint(playfield.Direction, t);
+ setAnchor(obj, playfield);
+ return obj;
+ }
+
+ private void addControlPoints(IList controlPoints, double sequenceStartTime)
+ {
+ controlPoints.ForEach(point => point.StartTime += sequenceStartTime);
+
+ scrollContainers.ForEach(container =>
{
- c.ControlPoints.Add(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } });
- c.ControlPoints.Add(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } });
- c.ControlPoints.Add(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } });
+ container.ControlPoints.AddRange(controlPoints);
});
- playfields.ForEach(p =>
+ foreach (var playfield in playfields)
{
- TestDrawableControlPoint createDrawablePoint(double t)
- {
- var obj = new TestDrawableControlPoint(p.Direction, t);
- setAnchor(obj, p);
- return obj;
- }
-
- p.Add(createDrawablePoint(time));
- p.Add(createDrawablePoint(time + 2000));
- p.Add(createDrawablePoint(time + 3000));
- });
+ foreach (var controlPoint in controlPoints)
+ playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime));
+ }
}
private void setAnchor(DrawableHitObject obj, TestPlayfield playfield)
@@ -236,7 +262,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AutoSizeAxes = Axes.Both;
- AddInternal(new Box { Size = new Vector2(75) });
+ AddInternal(new Box
+ {
+ Size = new Vector2(75),
+ Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1)
+ });
}
}
}
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 0014360d24..e181e1f431 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Edit
{
base.Update();
- if (EditorClock.CurrentTime != lastGridUpdateTime && blueprintContainer.CurrentTool != null)
+ if (EditorClock.CurrentTime != lastGridUpdateTime && !(blueprintContainer.CurrentTool is SelectTool))
showGridFor(Enumerable.Empty());
}
@@ -289,7 +289,11 @@ namespace osu.Game.Rulesets.Edit
=> beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime;
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
- => DurationToDistance(referenceTime, beatSnapProvider.SnapTime(DistanceToDuration(referenceTime, distance), referenceTime));
+ {
+ var snappedEndTime = beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime);
+
+ return DurationToDistance(referenceTime, snappedEndTime - referenceTime);
+ }
public override void UpdateHitObject(HitObject hitObject) => EditorBeatmap.UpdateHitObject(hitObject);
@@ -328,7 +332,7 @@ namespace osu.Game.Rulesets.Edit
/// Creates the applicable for a selection.
///
/// The selection.
- /// The for .
+ /// The for . If empty, a grid is returned for the current point in time.
[CanBeNull]
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null;
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
index 75ea3efdf2..0d4283e319 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
@@ -5,7 +5,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
{
public class ConstantScrollAlgorithm : IScrollAlgorithm
{
- public double GetDisplayStartTime(double time, double timeRange) => time - timeRange;
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
+ {
+ var adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
+ return adjustedTime - timeRange;
+ }
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
{
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
index 5f053975c7..c394a05bcc 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
@@ -6,15 +6,33 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
public interface IScrollAlgorithm
{
///
- /// Given a point in time, computes the time at which it enters the time range.
+ /// Given a point in time associated with an object's origin
+ /// and the spatial distance between the edge and the origin of the object along the scrolling axis,
+ /// computes the time at which the object initially enters the time range.
///
- ///
- /// E.g. For a constant time range of 5000ms, the time at which t=7000ms enters the time range is 2000ms.
- ///
- /// The point in time.
+ ///
+ /// Let's assume the following parameters:
+ ///
+ /// - = 7000ms,
+ /// - = 100px,
+ /// - = 5000ms,
+ /// - = 1000px
+ ///
+ /// and a constant scrolling rate.
+ /// To arrive at the end of the scrolling container, the object's origin has to cover
+ /// 1000 + 100 = 1100px
+ /// so that the edge starts at the end of the scrolling container.
+ /// One scroll length of 1000px covers 5000ms of time, so the time required to cover 1100px is equal to
+ /// 5000 * (1100 / 1000) = 5500ms,
+ /// and therefore the object should start being visible at
+ /// 7000 - 5500 = 1500ms.
+ ///
+ /// The time point at which the object origin should enter the time range.
+ /// The spatial distance between the object's edge and its origin along the scrolling axis.
/// The amount of visible time.
- /// The time at which enters .
- double GetDisplayStartTime(double time, double timeRange);
+ /// The absolute spatial length through .
+ /// The time at which the object should enter the time range.
+ double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength);
///
/// Computes the spatial length within a start and end time.
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
index fe22a86fad..7b827e0c63 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
@@ -20,11 +20,12 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
searchPoint = new MultiplierControlPoint();
}
- public double GetDisplayStartTime(double time, double timeRange)
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
{
+ var controlPoint = controlPointAt(originTime);
// The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases
- double visibleDuration = timeRange / controlPointAt(time).Multiplier;
- return time - visibleDuration;
+ double visibleDuration = (scrollLength + offset) * timeRange / controlPoint.Multiplier / scrollLength;
+ return originTime - visibleDuration;
}
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
index 3c9a205412..41f9ebdb82 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
@@ -20,7 +20,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
positionCache = new Dictionary();
}
- public double GetDisplayStartTime(double time, double timeRange) => time - timeRange - 1000;
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
+ {
+ double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
+ return adjustedTime - timeRange - 1000;
+ }
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
{
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 04b4374fc4..83a7f7289f 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -133,8 +133,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
break;
}
- var adjustedStartTime = scrollingInfo.Algorithm.TimeAt(-originAdjustment, hitObject.HitObject.StartTime, timeRange.Value, scrollLength);
- return scrollingInfo.Algorithm.GetDisplayStartTime(adjustedStartTime, timeRange.Value);
+ return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength);
}
// Cant use AddOnce() since the delegate is re-constructed every invocation
diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs
index 75bbb3e110..80bc3bdb87 100644
--- a/osu.Game/Tests/Visual/EditorTestScene.cs
+++ b/osu.Game/Tests/Visual/EditorTestScene.cs
@@ -24,8 +24,13 @@ namespace osu.Game.Tests.Visual
private void load()
{
Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo);
+ }
- LoadScreen(new Editor());
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddStep("Load editor", () => LoadScreen(new Editor()));
}
}
}
diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
index 161ebe7030..18326a78ad 100644
--- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs
+++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
@@ -86,8 +86,8 @@ namespace osu.Game.Tests.Visual
}
}
- public double GetDisplayStartTime(double time, double timeRange)
- => implementation.GetDisplayStartTime(time, timeRange);
+ public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
+ => implementation.GetDisplayStartTime(originTime, offset, timeRange, scrollLength);
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
=> implementation.GetLength(startTime, endTime, timeRange, scrollLength);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 81f59a4993..21c9eab4c6 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 8e359970a1..3ed25360c5 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -74,7 +74,7 @@
-
+
@@ -82,7 +82,7 @@
-
+