osu/osu.Game/Rulesets/UI/ScrollingPlayfield.cs

190 lines
7.7 KiB
C#
Raw Normal View History

2017-08-07 09:03:44 +00:00
// 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.Collections.Generic;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Framework.Lists;
2017-08-07 09:03:44 +00:00
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.UI
{
/// <summary>
2017-09-12 10:05:37 +00:00
/// A type of <see cref="Playfield"/> specialized towards scrolling <see cref="DrawableHitObject"/>s.
2017-08-07 09:03:44 +00:00
/// </summary>
public class ScrollingPlayfield : Playfield
2017-08-07 09:03:44 +00:00
{
/// <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 = 50;
/// <summary>
/// The span of time that is visible by the length of the scrolling axes.
2017-08-07 09:03:44 +00:00
/// 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)
2017-08-07 09:03:44 +00:00
{
Default = time_span_default,
MinValue = time_span_min,
MaxValue = time_span_max
};
/// <summary>
/// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s.
/// </summary>
public new readonly ScrollingHitObjectContainer HitObjects;
2017-08-07 09:03:44 +00:00
/// <summary>
2017-09-12 10:05:37 +00:00
/// Creates a new <see cref="ScrollingPlayfield"/>.
2017-08-07 09:03:44 +00:00
/// </summary>
/// <param name="scrollingAxes">The axes on which <see cref="DrawableHitObject"/>s in this container should scroll.</param>
/// <param name="customWidth">Whether we want our internal coordinate system to be scaled to a specified width</param>
protected ScrollingPlayfield(Direction scrollingDirection, float? customWidth = null)
2017-08-07 09:03:44 +00:00
: base(customWidth)
{
base.HitObjects = HitObjects = new ScrollingHitObjectContainer(scrollingDirection) { RelativeSizeAxes = Axes.Both };
HitObjects.TimeRange.BindTo(VisibleTimeRange);
2017-08-07 09:03:44 +00:00
}
private List<ScrollingPlayfield> nestedPlayfields;
2017-08-07 09:03:44 +00:00
/// <summary>
2017-09-12 10:05:37 +00:00
/// All the <see cref="ScrollingPlayfield"/>s nested inside this playfield.
2017-08-07 09:03:44 +00:00
/// </summary>
public IEnumerable<ScrollingPlayfield> NestedPlayfields => nestedPlayfields;
2017-08-07 09:03:44 +00:00
/// <summary>
2017-09-12 10:05:37 +00:00
/// Adds a <see cref="ScrollingPlayfield"/> to this playfield. The nested <see cref="ScrollingPlayfield"/>
2017-08-07 09:03:44 +00:00
/// will be given all of the same speed adjustments as this playfield.
/// </summary>
2017-09-12 10:05:37 +00:00
/// <param name="otherPlayfield">The <see cref="ScrollingPlayfield"/> to add.</param>
protected void AddNested(ScrollingPlayfield otherPlayfield)
2017-08-07 09:03:44 +00:00
{
if (nestedPlayfields == null)
nestedPlayfields = new List<ScrollingPlayfield>();
2017-08-07 09:03:44 +00:00
nestedPlayfields.Add(otherPlayfield);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (state.Keyboard.ControlPressed)
{
switch (args.Key)
{
case Key.Minus:
transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
break;
case Key.Plus:
transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
break;
}
}
return false;
}
private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None)
{
this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing));
}
private class TransformVisibleTimeRange : Transform<double, ScrollingPlayfield>
2017-08-07 09:03:44 +00:00
{
private double valueAt(double time)
{
if (time < StartTime) return StartValue;
if (time >= EndTime) return EndValue;
return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
}
public override string TargetMember => "VisibleTimeRange.Value";
protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time);
protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value;
2017-08-07 09:03:44 +00:00
}
public class ScrollingHitObjectContainer : HitObjectContainer
2017-08-07 09:03:44 +00:00
{
public readonly BindableDouble TimeRange = new BindableDouble
2017-08-07 09:03:44 +00:00
{
MinValue = 0,
MaxValue = double.MaxValue
};
public readonly SortedList<MultiplierControlPoint> ControlPoints = new SortedList<MultiplierControlPoint>();
private readonly Direction scrollingDirection;
2017-08-07 09:03:44 +00:00
public ScrollingHitObjectContainer(Direction scrollingDirection)
2017-08-07 09:03:44 +00:00
{
this.scrollingDirection = scrollingDirection;
RelativeSizeAxes = Axes.Both;
2017-08-07 09:03:44 +00:00
}
protected override void UpdateAfterChildren()
2017-08-22 07:01:19 +00:00
{
base.UpdateAfterChildren();
var currentMultiplier = controlPointAt(Time.Current);
2017-08-22 07:01:19 +00:00
foreach (var obj in AliveObjects)
2017-08-22 07:01:19 +00:00
{
var relativePosition = (Time.Current - obj.HitObject.StartTime) / (TimeRange / currentMultiplier.Multiplier);
// Todo: We may need to consider scale here
var finalPosition = (float)relativePosition * DrawSize;
2017-08-22 07:01:19 +00:00
switch (scrollingDirection)
{
case Direction.Horizontal:
obj.X = finalPosition.X;
break;
case Direction.Vertical:
obj.Y = finalPosition.Y;
break;
}
2017-08-22 07:01:19 +00:00
}
}
private readonly MultiplierControlPoint searchingPoint = new MultiplierControlPoint();
private MultiplierControlPoint controlPointAt(double time)
2017-08-07 09:03:44 +00:00
{
if (ControlPoints.Count == 0)
return new MultiplierControlPoint(double.MinValue);
2017-08-07 09:03:44 +00:00
if (time < ControlPoints[0].StartTime)
return ControlPoints[0];
2017-08-07 09:03:44 +00:00
searchingPoint.StartTime = time;
int index = ControlPoints.BinarySearch(searchingPoint);
if (index < 0)
index = ~index - 1;
return ControlPoints[index];
}
2017-08-07 09:03:44 +00:00
}
}
}