mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 20:07:29 +08:00
Merge pull request #7745 from bdach/overlapping-scroll-origin
Fix lifetime calculation in overlapping scroll algorithm
This commit is contained in:
commit
230435b5a3
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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<Mod>))]
|
||||
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
|
||||
|
||||
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<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
|
||||
{
|
||||
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<MultiplierControlPoint> 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)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -6,15 +6,33 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
public interface IScrollAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// E.g. For a constant time range of 5000ms, the time at which t=7000ms enters the time range is 2000ms.
|
||||
/// </remarks>
|
||||
/// <param name="time">The point in time.</param>
|
||||
/// <example>
|
||||
/// Let's assume the following parameters:
|
||||
/// <list type="bullet">
|
||||
/// <item><paramref name="originTime"/> = 7000ms,</item>
|
||||
/// <item><paramref name="offset"/> = 100px,</item>
|
||||
/// <item><paramref name="timeRange"/> = 5000ms,</item>
|
||||
/// <item><paramref name="scrollLength"/> = 1000px</item>
|
||||
/// </list>
|
||||
/// and a constant scrolling rate.
|
||||
/// To arrive at the end of the scrolling container, the object's origin has to cover
|
||||
/// <code>1000 + 100 = 1100px</code>
|
||||
/// 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
|
||||
/// <code>5000 * (1100 / 1000) = 5500ms,</code>
|
||||
/// and therefore the object should start being visible at
|
||||
/// <code>7000 - 5500 = 1500ms.</code>
|
||||
/// </example>
|
||||
/// <param name="originTime">The time point at which the object origin should enter the time range.</param>
|
||||
/// <param name="offset">The spatial distance between the object's edge and its origin along the scrolling axis.</param>
|
||||
/// <param name="timeRange">The amount of visible time.</param>
|
||||
/// <returns>The time at which <paramref name="time"/> enters <paramref name="timeRange"/>.</returns>
|
||||
double GetDisplayStartTime(double time, double timeRange);
|
||||
/// <param name="scrollLength">The absolute spatial length through <paramref name="timeRange"/>.</param>
|
||||
/// <returns>The time at which the object should enter the time range.</returns>
|
||||
double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the spatial length within a start and end time.
|
||||
|
@ -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)
|
||||
|
@ -20,7 +20,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
|
||||
positionCache = new Dictionary<double, double>();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user