1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-21 21:47:20 +08:00

Merge pull request #218 from Tom94/improved-sliders

Improved sliders
This commit is contained in:
Dean Herbert 2016-12-04 14:51:13 +09:00 committed by GitHub
commit 8bc5729f5f
9 changed files with 244 additions and 97 deletions

@ -1 +1 @@
Subproject commit 7ca1719b5cdc8b0a9600abe6472b38a426abedb0
Subproject commit 7b2f4dfce7894ca7dea7626dcc34bcc32df651b9

View File

@ -1,8 +1,10 @@
using System.Collections.Generic;
// Copyright (c) 2007-2016 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.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
@ -11,6 +13,9 @@ using osu.Framework.Input;
using OpenTK.Graphics.ES30;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Framework.Configuration;
using System;
namespace osu.Game.Modes.Osu.Objects.Drawables
{
@ -49,30 +54,77 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
};
}
private Bindable<bool> snakingIn;
private Bindable<bool> snakingOut;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
snakingIn = config.GetBindable<bool>(OsuConfig.SnakingInSliders);
snakingOut = config.GetBindable<bool>(OsuConfig.SnakingOutSliders);
}
protected override void LoadComplete()
{
base.LoadComplete();
//force application of the state that was set before we loaded.
UpdateState(State);
body.PathWidth = 32;
}
private void computeProgress(out int repeat, out double progress)
{
progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
repeat = (int)(progress * slider.RepeatCount);
progress = (progress * slider.RepeatCount) % 1;
if (repeat % 2 == 1)
progress = 1 - progress;
}
private void updateBall(double progress)
{
ball.Alpha = Time.Current >= slider.StartTime && Time.Current <= slider.EndTime ? 1 : 0;
ball.Position = slider.Curve.PositionAt(progress);
}
private void updateBody(int repeat, double progress)
{
double drawStartProgress = 0;
double drawEndProgress = MathHelper.Clamp((Time.Current - slider.StartTime + TIME_PREEMPT) / TIME_FADEIN, 0, 1);
if (repeat >= slider.RepeatCount - 1)
{
if (Math.Min(repeat, slider.RepeatCount - 1) % 2 == 1)
{
drawStartProgress = 0;
drawEndProgress = progress;
}
else
{
drawStartProgress = progress;
drawEndProgress = 1;
}
}
body.SetRange(
snakingOut ? drawStartProgress : 0,
snakingIn ? drawEndProgress : 1);
}
protected override void Update()
{
base.Update();
ball.Alpha = Time.Current >= slider.StartTime && Time.Current <= slider.EndTime ? 1 : 0;
double progress;
int repeat;
computeProgress(out repeat, out progress);
double t = (Time.Current - slider.StartTime) / slider.Duration;
if (slider.RepeatCount > 1)
{
int currentRepeat = (int)(t * slider.RepeatCount);
t = (t * slider.RepeatCount) % 1;
if (currentRepeat % 2 == 1)
t = 1 - t;
}
ball.Position = slider.Curve.PositionAt(t);
updateBall(progress);
updateBody(repeat, progress);
}
protected override void CheckJudgement(bool userTriggered)
@ -187,7 +239,14 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
private Path path;
private BufferedContainer container;
private double? drawnProgress;
public float PathWidth
{
get { return path.PathWidth; }
set { path.PathWidth = value; }
}
private double? drawnProgressStart;
private double? drawnProgressEnd;
private Slider slider;
public Body(Slider s)
@ -221,69 +280,38 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
path.Texture = textures.Get(@"Menu/logo");
}
protected override void LoadComplete()
public void SetRange(double p0, double p1)
{
base.LoadComplete();
path.PathWidth = 32;
}
if (p0 > p1)
MathHelper.Swap(ref p0, ref p1);
protected override void Update()
{
base.Update();
if (updateSnaking())
if (updateSnaking(p0, p1))
{
// Autosizing does not give us the desired behaviour here.
// We want the container to have the same size as the slider,
// and to be positioned such that the slider head is at (0,0).
container.Size = path.Size;
container.Position = -path.HeadPosition;
container.Position = -path.PositionInBoundingBox(slider.Curve.PositionAt(0) - currentCurve[0]);
container.ForceRedraw();
}
}
private bool updateSnaking()
private List<Vector2> currentCurve = new List<Vector2>();
private bool updateSnaking(double p0, double p1)
{
double progress = MathHelper.Clamp((Time.Current - slider.StartTime + TIME_PREEMPT) / TIME_FADEIN, 0, 1);
if (drawnProgressStart == p0 && drawnProgressEnd == p1) return false;
if (progress == drawnProgress) return false;
drawnProgressStart = p0;
drawnProgressEnd = p1;
bool madeChanges = false;
if (progress == 0)
{
//if we have gone backwards, just clear the path for now.
drawnProgress = 0;
path.ClearVertices();
madeChanges = true;
}
slider.Curve.GetPathToProgress(currentCurve, p0, p1);
Vector2 startPosition = slider.Curve.PositionAt(0);
path.ClearVertices();
foreach (Vector2 p in currentCurve)
path.AddVertex(p - currentCurve[0]);
if (drawnProgress == null)
{
drawnProgress = 0;
path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition);
madeChanges = true;
}
double segmentSize = 1 / (slider.Curve.Length / 5);
while (drawnProgress + segmentSize < progress)
{
drawnProgress += segmentSize;
path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition);
madeChanges = true;
}
if (progress == 1 && drawnProgress != progress)
{
drawnProgress = progress;
path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition);
madeChanges = true;
}
return madeChanges;
return true;
}
}
}

View File

@ -1,5 +1,12 @@
using System.Collections.Generic;
// Copyright (c) 2007-2016 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 OpenTK;
using System.Linq;
using System.Diagnostics;
using osu.Framework.MathUtils;
using System;
namespace osu.Game.Modes.Osu.Objects
{
@ -11,7 +18,8 @@ namespace osu.Game.Modes.Osu.Objects
public CurveTypes CurveType;
private List<Vector2> calculatedPath;
private List<Vector2> calculatedPath = new List<Vector2>();
private List<double> cumulativeLength = new List<double>();
private List<Vector2> calculateSubpath(List<Vector2> subpath)
{
@ -24,9 +32,9 @@ namespace osu.Game.Modes.Osu.Objects
}
}
public void Calculate()
private void calculatePath()
{
calculatedPath = new List<Vector2>();
calculatedPath.Clear();
// Sliders may consist of various subpaths separated by two consecutive vertices
// with the same position. The following loop parses these subpaths and computes
@ -50,18 +58,126 @@ namespace osu.Game.Modes.Osu.Objects
}
}
private void calculateCumulativeLengthAndTrimPath()
{
double l = 0;
cumulativeLength.Clear();
cumulativeLength.Add(l);
for (int i = 0; i < calculatedPath.Count - 1; ++i)
{
Vector2 diff = calculatedPath[i + 1] - calculatedPath[i];
double d = diff.Length;
// Shorten slider curves that are too long compared to what's
// in the .osu file.
if (Length - l < d)
{
calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Length - l) / d);
calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i);
l = Length;
cumulativeLength.Add(l);
break;
}
l += d;
cumulativeLength.Add(l);
}
// Lengthen slider curves that are too short compared to what's
// in the .osu file.
if (l < Length && calculatedPath.Count > 1)
{
Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2];
double d = diff.Length;
if (d <= 0)
return;
calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d);
cumulativeLength[calculatedPath.Count - 1] = Length;
}
}
public void Calculate()
{
calculatePath();
calculateCumulativeLengthAndTrimPath();
}
private int indexOfDistance(double d)
{
int i = cumulativeLength.BinarySearch(d);
if (i < 0) i = ~i;
return i;
}
private double progressToDistance(double progress)
{
return MathHelper.Clamp(progress, 0, 1) * Length;
}
private Vector2 interpolateVertices(int i, double d)
{
if (calculatedPath.Count == 0)
return Vector2.Zero;
if (i <= 0)
return calculatedPath.First();
else if (i >= calculatedPath.Count)
return calculatedPath.Last();
Vector2 p0 = calculatedPath[i - 1];
Vector2 p1 = calculatedPath[i];
double d0 = cumulativeLength[i - 1];
double d1 = cumulativeLength[i];
// Avoid division by and almost-zero number in case two points are extremely close to each other.
if (Precision.AlmostEquals(d0, d1))
return p0;
double w = (d - d0) / (d1 - d0);
return p0 + (p1 - p0) * (float)w;
}
/// <summary>
/// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider)
/// to 1 (end of the slider) and stores the generated path in the given list.
/// </summary>
/// <param name="path">The list to be filled with the computed curve.</param>
/// <param name="progress">Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
public void GetPathToProgress(List<Vector2> path, double p0, double p1)
{
double d0 = progressToDistance(p0);
double d1 = progressToDistance(p1);
path.Clear();
int i = 0;
for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i);
path.Add(interpolateVertices(i, d0));
for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i)
path.Add(calculatedPath[i]);
path.Add(interpolateVertices(i, d1));
}
/// <summary>
/// Computes the position on the slider at a given progress that ranges from 0 (beginning of the slider)
/// to 1 (end of the slider).
/// </summary>
/// <param name="progress">Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
/// <returns></returns>
public Vector2 PositionAt(double progress)
{
progress = MathHelper.Clamp(progress, 0, 1);
double index = progress * (calculatedPath.Count - 1);
int flooredIndex = (int)index;
Vector2 pos = calculatedPath[flooredIndex];
if (index != flooredIndex)
pos += (calculatedPath[flooredIndex + 1] - pos) * (float)(index - flooredIndex);
return pos;
double d = progressToDistance(progress);
return interpolateVertices(indexOfDistance(d), d);
}
}
}

View File

@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps
double mult = 1;
if (applyMultipliers && samplePoint > point && ControlPoints[samplePoint].BeatLength < 0)
if (applyMultipliers && samplePoint > point)
mult = ControlPoints[samplePoint].VelocityAdjustment;
return ControlPoints[point].BeatLength * mult;

View File

@ -4,12 +4,10 @@
using osu.Framework.Configuration;
using osu.Framework.Platform;
using osu.Game.Modes;
using osu.Game.Online.API;
using osu.Game.Screens.Play;
namespace osu.Game.Configuration
{
class OsuConfigManager : ConfigManager<OsuConfig>
public class OsuConfigManager : ConfigManager<OsuConfig>
{
protected override void InitialiseDefaults()
{
@ -129,7 +127,8 @@ namespace osu.Game.Configuration
//Set(OsuConfig.Skin, SkinManager.DEFAULT_SKIN);
Set(OsuConfig.SkinSamples, true);
Set(OsuConfig.SkipTablet, false);
Set(OsuConfig.SnakingSliders, true);
Set(OsuConfig.SnakingInSliders, true);
Set(OsuConfig.SnakingOutSliders, false);
Set(OsuConfig.Tablet, false);
Set(OsuConfig.UpdatePending, false);
Set(OsuConfig.UseSkinCursor, false);
@ -189,7 +188,7 @@ namespace osu.Game.Configuration
}
}
enum OsuConfig
public enum OsuConfig
{
// New osu:
PlayMode,
@ -303,7 +302,8 @@ namespace osu.Game.Configuration
Skin,
SkinSamples,
SkipTablet,
SnakingSliders,
SnakingInSliders,
SnakingOutSliders,
Tablet,
UpdatePending,
UserFilter,
@ -345,5 +345,6 @@ namespace osu.Game.Configuration
Ticker,
CompatibilityContext,
CanForceOptimusCompatibility,
}
}

View File

@ -87,9 +87,9 @@ namespace osu.Game.Modes.Objects.Drawables
//todo: consider making abstract.
}
protected override void Update()
protected override void UpdateAfterChildren()
{
base.Update();
base.UpdateAfterChildren();
UpdateJudgement(false);
}

View File

@ -1,12 +1,12 @@
using System;
// Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.GameModes;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
@ -15,11 +15,7 @@ using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Processing;
using osu.Game.IPC;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Online.API.Requests;
namespace osu.Game
{

View File

@ -1,8 +1,9 @@
using osu.Framework;
// Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Configuration;
namespace osu.Game.Overlays.Options.Graphics
@ -18,8 +19,13 @@ namespace osu.Game.Overlays.Options.Graphics
{
new CheckBoxOption
{
LabelText = "Snaking sliders",
Bindable = config.GetBindable<bool>(OsuConfig.SnakingSliders)
LabelText = "Snaking in sliders",
Bindable = config.GetBindable<bool>(OsuConfig.SnakingInSliders)
},
new CheckBoxOption
{
LabelText = "Snaking out sliders",
Bindable = config.GetBindable<bool>(OsuConfig.SnakingOutSliders)
},
new CheckBoxOption
{

View File

@ -106,9 +106,9 @@ namespace osu.Game.Overlays.Toolbar
private Cached activeMode = new Cached();
protected override void UpdateLayout()
protected override void UpdateAfterChildren()
{
base.UpdateLayout();
base.UpdateAfterChildren();
if (!activeMode.EnsureValid())
activeMode.Refresh(() => modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, EasingTypes.OutQuint));