mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'more-global-scrollalgo' into note-placement
This commit is contained in:
27
osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs
Normal file
27
osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
||||
|
||||
namespace osu.Game.Rulesets.UI.Scrolling
|
||||
{
|
||||
public interface IScrollingInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The direction <see cref="HitObject"/>s should scroll in.
|
||||
/// </summary>
|
||||
IBindable<ScrollingDirection> Direction { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
IBindable<double> TimeRange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The algorithm which controls <see cref="HitObject"/> positions and sizes.
|
||||
/// </summary>
|
||||
IScrollAlgorithm Algorithm { get; }
|
||||
}
|
||||
}
|
@ -1,59 +1,39 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Configuration;
|
||||
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 : HitObjectContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// The duration required to scroll through one length of the <see cref="ScrollingHitObjectContainer"/> before any control point adjustments.
|
||||
/// </summary>
|
||||
public readonly BindableDouble TimeRange = new BindableDouble
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = double.MaxValue
|
||||
};
|
||||
private readonly IBindable<double> timeRange = new BindableDouble();
|
||||
|
||||
/// <summary>
|
||||
/// The control points that adjust the scrolling speed.
|
||||
/// </summary>
|
||||
protected readonly SortedList<MultiplierControlPoint> ControlPoints = new SortedList<MultiplierControlPoint>();
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
private readonly IScrollAlgorithm algorithm;
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
private Cached initialStateCache = new Cached();
|
||||
|
||||
public ScrollingHitObjectContainer(ScrollVisualisationMethod visualisationMethod)
|
||||
public ScrollingHitObjectContainer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
TimeRange.ValueChanged += _ => initialStateCache.Invalidate();
|
||||
Direction.ValueChanged += _ => initialStateCache.Invalidate();
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
timeRange.BindTo(scrollingInfo.TimeRange);
|
||||
|
||||
switch (visualisationMethod)
|
||||
{
|
||||
case ScrollVisualisationMethod.Sequential:
|
||||
algorithm = new SequentialScrollAlgorithm(ControlPoints);
|
||||
break;
|
||||
case ScrollVisualisationMethod.Overlapping:
|
||||
algorithm = new OverlappingScrollAlgorithm(ControlPoints);
|
||||
break;
|
||||
case ScrollVisualisationMethod.Constant:
|
||||
algorithm = new ConstantScrollAlgorithm();
|
||||
break;
|
||||
}
|
||||
direction.ValueChanged += _ => initialStateCache.Invalidate();
|
||||
timeRange.ValueChanged += _ => initialStateCache.Invalidate();
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject hitObject)
|
||||
@ -70,20 +50,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
return result;
|
||||
}
|
||||
|
||||
public void AddControlPoint(MultiplierControlPoint controlPoint)
|
||||
{
|
||||
ControlPoints.Add(controlPoint);
|
||||
initialStateCache.Invalidate();
|
||||
}
|
||||
|
||||
public bool RemoveControlPoint(MultiplierControlPoint controlPoint)
|
||||
{
|
||||
var result = ControlPoints.Remove(controlPoint);
|
||||
if (result)
|
||||
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)
|
||||
@ -100,7 +66,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
|
||||
if (!initialStateCache.IsValid)
|
||||
{
|
||||
switch (Direction.Value)
|
||||
switch (direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
case ScrollingDirection.Down:
|
||||
@ -111,7 +77,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
break;
|
||||
}
|
||||
|
||||
algorithm.Reset();
|
||||
scrollingInfo.Algorithm.Reset();
|
||||
|
||||
foreach (var obj in Objects)
|
||||
computeInitialStateRecursive(obj);
|
||||
@ -121,19 +87,19 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
|
||||
private void computeInitialStateRecursive(DrawableHitObject hitObject)
|
||||
{
|
||||
hitObject.LifetimeStart = algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, TimeRange);
|
||||
hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value);
|
||||
|
||||
if (hitObject.HitObject is IHasEndTime endTime)
|
||||
{
|
||||
switch (Direction.Value)
|
||||
switch (direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
case ScrollingDirection.Down:
|
||||
hitObject.Height = algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, TimeRange, scrollLength);
|
||||
hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength);
|
||||
break;
|
||||
case ScrollingDirection.Left:
|
||||
case ScrollingDirection.Right:
|
||||
hitObject.Width = algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, TimeRange, scrollLength);
|
||||
hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -158,19 +124,19 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
|
||||
private void updatePosition(DrawableHitObject hitObject, double currentTime)
|
||||
{
|
||||
switch (Direction.Value)
|
||||
switch (direction.Value)
|
||||
{
|
||||
case ScrollingDirection.Up:
|
||||
hitObject.Y = algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength);
|
||||
hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
||||
break;
|
||||
case ScrollingDirection.Down:
|
||||
hitObject.Y = -algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength);
|
||||
hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
||||
break;
|
||||
case ScrollingDirection.Left:
|
||||
hitObject.X = algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength);
|
||||
hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
||||
break;
|
||||
case ScrollingDirection.Right:
|
||||
hitObject.X = -algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength);
|
||||
hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,6 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.UI.Scrolling
|
||||
@ -14,88 +10,19 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
/// <summary>
|
||||
/// A type of <see cref="Playfield"/> specialized towards scrolling <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
public abstract class ScrollingPlayfield : Playfield, IKeyBindingHandler<GlobalAction>
|
||||
public abstract class ScrollingPlayfield : Playfield
|
||||
{
|
||||
/// <summary>
|
||||
/// The default span of time visible by the length of the scrolling axes.
|
||||
/// This is clamped between <see cref="time_span_min"/> and <see cref="time_span_max"/>.
|
||||
/// </summary>
|
||||
private const double time_span_default = 1500;
|
||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
/// <summary>
|
||||
/// The minimum span of time that may be visible by the length of the scrolling axes.
|
||||
/// </summary>
|
||||
private const double time_span_min = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum span of time that may be visible by the length of the scrolling axes.
|
||||
/// </summary>
|
||||
private const double time_span_max = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// The step increase/decrease of the span of time visible by the length of the scrolling axes.
|
||||
/// </summary>
|
||||
private const double time_span_step = 200;
|
||||
|
||||
/// <summary>
|
||||
/// The span of time that is visible by the length of the scrolling axes.
|
||||
/// For example, only hit objects with start time less than or equal to 1000 will be visible with <see cref="VisibleTimeRange"/> = 1000.
|
||||
/// </summary>
|
||||
public readonly BindableDouble VisibleTimeRange = new BindableDouble(time_span_default)
|
||||
{
|
||||
Default = time_span_default,
|
||||
MinValue = time_span_min,
|
||||
MaxValue = time_span_max
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Whether the player can change <see cref="VisibleTimeRange"/>.
|
||||
/// </summary>
|
||||
protected virtual bool UserScrollSpeedAdjustment => true;
|
||||
|
||||
/// <summary>
|
||||
/// The container that contains the <see cref="DrawableHitObject"/>s.
|
||||
/// </summary>
|
||||
public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)HitObjectContainer;
|
||||
|
||||
/// <summary>
|
||||
/// The direction in which <see cref="DrawableHitObject"/>s in this <see cref="ScrollingPlayfield"/> should scroll.
|
||||
/// </summary>
|
||||
protected readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential;
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
HitObjects.TimeRange.BindTo(VisibleTimeRange);
|
||||
Direction.BindTo(scrollingInfo.Direction);
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (!UserScrollSpeedAdjustment)
|
||||
return false;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.IncreaseScrollSpeed:
|
||||
this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
|
||||
return true;
|
||||
case GlobalAction.DecreaseScrollSpeed:
|
||||
this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnReleased(GlobalAction action) => false;
|
||||
|
||||
protected sealed override HitObjectContainer CreateHitObjectContainer()
|
||||
{
|
||||
var container = new ScrollingHitObjectContainer(VisualisationMethod);
|
||||
container.Direction.BindTo(Direction);
|
||||
return container;
|
||||
}
|
||||
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer();
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Timing;
|
||||
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
||||
|
||||
namespace osu.Game.Rulesets.UI.Scrolling
|
||||
{
|
||||
@ -18,20 +23,82 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
/// A type of <see cref="RulesetContainer{TPlayfield,TObject}"/> that supports a <see cref="ScrollingPlayfield"/>.
|
||||
/// <see cref="HitObject"/>s inside this <see cref="RulesetContainer{TPlayfield,TObject}"/> will scroll within the playfield.
|
||||
/// </summary>
|
||||
public abstract class ScrollingRulesetContainer<TPlayfield, TObject> : RulesetContainer<TPlayfield, TObject>
|
||||
public abstract class ScrollingRulesetContainer<TPlayfield, TObject> : RulesetContainer<TPlayfield, TObject>, IKeyBindingHandler<GlobalAction>
|
||||
where TObject : HitObject
|
||||
where TPlayfield : ScrollingPlayfield
|
||||
{
|
||||
/// <summary>
|
||||
/// The default span of time visible by the length of the scrolling axes.
|
||||
/// This is clamped between <see cref="time_span_min"/> and <see cref="time_span_max"/>.
|
||||
/// </summary>
|
||||
private const double time_span_default = 1500;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum span of time that may be visible by the length of the scrolling axes.
|
||||
/// </summary>
|
||||
private const double time_span_min = 50;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum span of time that may be visible by the length of the scrolling axes.
|
||||
/// </summary>
|
||||
private const double time_span_max = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// The step increase/decrease of the span of time visible by the length of the scrolling axes.
|
||||
/// </summary>
|
||||
private const double time_span_step = 200;
|
||||
|
||||
protected readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||
|
||||
/// <summary>
|
||||
/// The span of time that is visible by the length of the scrolling axes.
|
||||
/// For example, only hit objects with start time less than or equal to 1000 will be visible with <see cref="TimeRange"/> = 1000.
|
||||
/// </summary>
|
||||
protected readonly BindableDouble TimeRange = new BindableDouble(time_span_default)
|
||||
{
|
||||
Default = time_span_default,
|
||||
MinValue = time_span_min,
|
||||
MaxValue = time_span_max
|
||||
};
|
||||
|
||||
protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the player can change <see cref="VisibleTimeRange"/>.
|
||||
/// </summary>
|
||||
protected virtual bool UserScrollSpeedAdjustment => true;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the default <see cref="MultiplierControlPoint"/>s that adjust the scrolling rate of <see cref="HitObject"/>s
|
||||
/// inside this <see cref="RulesetContainer{TPlayfield,TObject}"/>.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected readonly SortedList<MultiplierControlPoint> DefaultControlPoints = new SortedList<MultiplierControlPoint>(Comparer<MultiplierControlPoint>.Default);
|
||||
private readonly SortedList<MultiplierControlPoint> controlPoints = new SortedList<MultiplierControlPoint>(Comparer<MultiplierControlPoint>.Default);
|
||||
|
||||
protected IScrollingInfo ScrollingInfo => scrollingInfo;
|
||||
|
||||
[Cached(Type = typeof(IScrollingInfo))]
|
||||
private readonly LocalScrollingInfo scrollingInfo;
|
||||
|
||||
protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
scrollingInfo = new LocalScrollingInfo();
|
||||
scrollingInfo.Direction.BindTo(Direction);
|
||||
scrollingInfo.TimeRange.BindTo(TimeRange);
|
||||
|
||||
switch (VisualisationMethod)
|
||||
{
|
||||
case ScrollVisualisationMethod.Sequential:
|
||||
scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints);
|
||||
break;
|
||||
case ScrollVisualisationMethod.Overlapping:
|
||||
scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints);
|
||||
break;
|
||||
case ScrollVisualisationMethod.Constant:
|
||||
scrollingInfo.Algorithm = new ConstantScrollAlgorithm();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -75,19 +142,40 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
// Collapse sections with the same start time
|
||||
.GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime);
|
||||
|
||||
DefaultControlPoints.AddRange(timingChanges);
|
||||
controlPoints.AddRange(timingChanges);
|
||||
|
||||
// If we have no control points, add a default one
|
||||
if (DefaultControlPoints.Count == 0)
|
||||
DefaultControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier });
|
||||
|
||||
DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield));
|
||||
if (controlPoints.Count == 0)
|
||||
controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier });
|
||||
}
|
||||
|
||||
private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield playfield)
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
playfield.HitObjects.AddControlPoint(controlPoint);
|
||||
playfield.NestedPlayfields?.OfType<ScrollingPlayfield>().ForEach(p => applySpeedAdjustment(controlPoint, p));
|
||||
if (!UserScrollSpeedAdjustment)
|
||||
return false;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.IncreaseScrollSpeed:
|
||||
this.TransformBindableTo(TimeRange, TimeRange - time_span_step, 200, Easing.OutQuint);
|
||||
return true;
|
||||
case GlobalAction.DecreaseScrollSpeed:
|
||||
this.TransformBindableTo(TimeRange, TimeRange + time_span_step, 200, Easing.OutQuint);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OnReleased(GlobalAction action) => false;
|
||||
|
||||
private class LocalScrollingInfo : IScrollingInfo
|
||||
{
|
||||
public IBindable<ScrollingDirection> Direction { get; } = new Bindable<ScrollingDirection>();
|
||||
|
||||
public IBindable<double> TimeRange { get; } = new BindableDouble();
|
||||
|
||||
public IScrollAlgorithm Algorithm { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user