mirror of
https://github.com/ppy/osu.git
synced 2025-03-23 08:27:23 +08:00
Split out scrolling algorithm
This commit is contained in:
parent
2d345b2f80
commit
117ab8a26d
@ -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);
|
||||
}
|
||||
}
|
||||
|
19
osu.Game/Rulesets/UI/HitObjectContainer.cs
Normal file
19
osu.Game/Rulesets/UI/HitObjectContainer.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// 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<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>();
|
||||
public virtual IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>();
|
||||
|
||||
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
|
||||
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);
|
||||
}
|
||||
}
|
@ -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
|
||||
/// <summary>
|
||||
/// The HitObjects contained in this Playfield.
|
||||
/// </summary>
|
||||
public HitObjectContainer HitObjects { get; protected set; }
|
||||
public readonly HitObjectContainer HitObjects;
|
||||
|
||||
public Container<Drawable> 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
|
||||
/// <param name="judgement">The <see cref="Judgement"/> that occurred.</param>
|
||||
public virtual void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { }
|
||||
|
||||
public class HitObjectContainer : CompositeDrawable
|
||||
{
|
||||
public virtual IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>();
|
||||
public virtual IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>();
|
||||
|
||||
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
|
||||
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates the container that will be used to contain the <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer();
|
||||
|
||||
private class ScaledContainer : Container
|
||||
{
|
||||
|
@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// 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<DrawableHitObject, double> hitObjectPositions = new Dictionary<DrawableHitObject, double>();
|
||||
|
||||
private readonly IReadOnlyList<MultiplierControlPoint> controlPoints;
|
||||
|
||||
public GlobalScrollingAlgorithm(IReadOnlyList<MultiplierControlPoint> controlPoints)
|
||||
{
|
||||
this.controlPoints = controlPoints;
|
||||
}
|
||||
|
||||
public void ComputeInitialStates(IEnumerable<DrawableHitObject> 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<DrawableHitObject> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// 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<DrawableHitObject> hitObjects, ScrollingDirection direction, double timeRange, Vector2 length);
|
||||
void ComputePositions(IEnumerable<DrawableHitObject> hitObjects, ScrollingDirection direction, double currentTime, double timeRange, Vector2 length);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// 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);
|
||||
}
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// 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<MultiplierControlPoint> ControlPoints = new SortedList<MultiplierControlPoint>();
|
||||
|
||||
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<MultiplierControlPoint> controlPoints = new SortedList<MultiplierControlPoint>();
|
||||
|
||||
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<DrawableHitObject, double> hitObjectPositions = new Dictionary<DrawableHitObject, double>();
|
||||
|
||||
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;
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates the algorithm that will process the positions of the <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
protected abstract IScrollingAlgorithm CreateScrollingAlgorithm();
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
/// <summary>
|
||||
/// A type of <see cref="Playfield"/> specialized towards scrolling <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
public class ScrollingPlayfield : Playfield
|
||||
public abstract class ScrollingPlayfield : Playfield
|
||||
{
|
||||
/// <summary>
|
||||
/// The default span of time visible by the length of the scrolling axes.
|
||||
@ -49,7 +49,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
/// <summary>
|
||||
/// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
public new readonly ScrollingHitObjectContainer HitObjects;
|
||||
public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects;
|
||||
|
||||
private readonly ScrollingDirection direction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="ScrollingPlayfield"/>.
|
||||
@ -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();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <see cref="ScrollingHitObjectContainer"/> that will handle the scrolling of the <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual ScrollingHitObjectContainer CreateScrollingHitObjectContainer() => new GlobalScrollingHitObjectContainer(direction);
|
||||
|
||||
private class TransformVisibleTimeRange : Transform<double, ScrollingPlayfield>
|
||||
{
|
||||
private double valueAt(double time)
|
||||
|
@ -315,6 +315,10 @@
|
||||
<Compile Include="Rulesets\Mods\IApplicableMod.cs" />
|
||||
<Compile Include="Rulesets\Mods\IApplicableToBeatmapConverter.cs" />
|
||||
<Compile Include="Rulesets\Mods\IApplicableToDrawableHitObject.cs" />
|
||||
<Compile Include="Rulesets\UI\HitObjectContainer.cs" />
|
||||
<Compile Include="Rulesets\UI\Scrolling\Algorithms\GlobalScrollingAlgorithm.cs" />
|
||||
<Compile Include="Rulesets\UI\Scrolling\Algorithms\IScrollingAlgorithm.cs" />
|
||||
<Compile Include="Rulesets\UI\Scrolling\GlobalScrollingHitObjectContainer.cs" />
|
||||
<Compile Include="Rulesets\UI\Scrolling\ScrollingDirection.cs" />
|
||||
<Compile Include="Rulesets\UI\Scrolling\ScrollingHitObjectContainer.cs" />
|
||||
<Compile Include="Rulesets\UI\Scrolling\ScrollingPlayfield.cs" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user