1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 14:12:55 +08:00

Initial game-wide update with the new SliderPath

This commit is contained in:
smoogipoo 2019-12-05 19:53:31 +09:00
parent c9a66c0d07
commit 3ebbf62b2a
16 changed files with 97 additions and 111 deletions

View File

@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep($"move mouse to control point {index}", () =>
{
Vector2 position = slider.Position + slider.Path.ControlPoints[index];
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
});
}

View File

@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
base.Update();
Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
Position = slider.StackedPosition + slider.Path.ControlPoints[Index].Position.Value;
updateMarkerDisplay();
updateConnectingPath();
@ -116,10 +116,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
path.ClearVertices();
if (Index != slider.Path.ControlPoints.Length - 1)
if (Index != slider.Path.ControlPoints.Count - 1)
{
path.AddVertex(Vector2.Zero);
path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
path.AddVertex(slider.Path.ControlPoints[Index + 1].Position.Value - slider.Path.ControlPoints[Index].Position.Value);
}
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
@ -156,8 +156,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnDrag(DragEvent e)
{
var newControlPoints = slider.Path.ControlPoints.ToArray();
if (Index == 0)
{
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
@ -168,29 +166,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.StartTime = snappedTime;
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
for (int i = 1; i < newControlPoints.Length; i++)
newControlPoints[i] -= movementDelta;
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
}
else
newControlPoints[Index] += e.Delta;
if (isSegmentSeparatorWithNext)
newControlPoints[Index + 1] = newControlPoints[Index];
if (isSegmentSeparatorWithPrevious)
newControlPoints[Index - 1] = newControlPoints[Index];
ControlPointsChanged?.Invoke(newControlPoints);
slider.Path.ControlPoints[Index].Position.Value += e.Delta;
return true;
}
protected override bool OnDragEnd(DragEndEvent e) => true;
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index];
private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index];
private bool isSegmentSeparator => slider.Path.ControlPoints[Index].Type.Value.HasValue;
}
}

View File

@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
base.Update();
while (slider.Path.ControlPoints.Length > Pieces.Count)
while (slider.Path.ControlPoints.Count > Pieces.Count)
{
var piece = new PathControlPointPiece(slider, Pieces.Count)
{
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
Pieces.Add(piece);
}
while (slider.Path.ControlPoints.Length < Pieces.Count)
while (slider.Path.ControlPoints.Count < Pieces.Count)
Pieces.Remove(Pieces[Pieces.Count - 1]);
}
@ -105,29 +105,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private bool deleteSelected()
{
var newControlPoints = new List<Vector2>();
int countDeleted = 0;
foreach (var piece in Pieces)
{
if (!piece.IsSelected.Value)
newControlPoints.Add(slider.Path.ControlPoints[piece.Index]);
if (piece.IsSelected.Value)
{
slider.Path.ControlPoints.RemoveAt(piece.Index);
countDeleted++;
}
}
// Ensure that there are any points to be deleted
if (newControlPoints.Count == slider.Path.ControlPoints.Length)
if (countDeleted == 0)
return false;
// If there are 0 remaining control points, treat the slider as being deleted
if (newControlPoints.Count == 0)
if (slider.Path.ControlPoints.Count == 0)
{
placementHandler?.Delete(slider);
return true;
}
// Make control points relative
Vector2 first = newControlPoints[0];
for (int i = 0; i < newControlPoints.Count; i++)
newControlPoints[i] = newControlPoints[i] - first;
Vector2 first = slider.Path.ControlPoints[0].Position.Value;
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
slider.Path.ControlPoints[i].Position.Value = slider.Path.ControlPoints[i].Position.Value - first;
// The slider's position defines the position of the first control point, and all further control points are relative to that point
slider.Position += first;
@ -136,7 +139,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
foreach (var piece in Pieces)
piece.IsSelected.Value = false;
ControlPointsChanged?.Invoke(newControlPoints.ToArray());
return true;
}

View File

@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private HitCirclePiece headCirclePiece;
private HitCirclePiece tailCirclePiece;
private readonly List<Segment> segments = new List<Segment>();
private Vector2 cursor;
private InputManager inputManager;
private PlacementState state;
@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
: base(new Objects.Slider())
{
RelativeSizeAxes = Axes.Both;
segments.Add(new Segment(Vector2.Zero));
HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = Vector2.Zero } });
}
[BackgroundDependencyLoader]
@ -74,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
case PlacementState.Body:
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
// is used instead since snapping control points doesn't make much sense
cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
break;
}
}
@ -91,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
switch (e.Button)
{
case MouseButton.Left:
segments.Last().ControlPoints.Add(cursor);
HitObject.Path.ControlPoints.Add(new PathControlPoint { Position = { Value = HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 1].Position.Value } });
break;
}
@ -110,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override bool OnDoubleClick(DoubleClickEvent e)
{
segments.Add(new Segment(segments[segments.Count - 1].ControlPoints.Last()));
HitObject.Path.ControlPoints[HitObject.Path.ControlPoints.Count - 2].Type.Value = PathType.Bezier;
return true;
}
@ -134,12 +132,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updateSlider()
{
Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance);
HitObject.Path.ExpectedDistance.Value = null;
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance;
bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle);

View File

@ -77,12 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
Debug.Assert(placementControlPointIndex != null);
Vector2 position = e.MousePosition - HitObject.Position;
var controlPoints = HitObject.Path.ControlPoints.ToArray();
controlPoints[placementControlPointIndex.Value] = position;
onNewControlPoints(controlPoints);
HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position;
return true;
}
@ -97,15 +92,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
position -= HitObject.Position;
var controlPoints = new Vector2[HitObject.Path.ControlPoints.Length + 1];
HitObject.Path.ControlPoints.CopyTo(controlPoints);
int insertionIndex = 0;
float minDistance = float.MaxValue;
for (int i = 0; i < controlPoints.Length - 2; i++)
for (int i = 0; i < HitObject.Path.ControlPoints.Count - 2; i++)
{
float dist = new Line(controlPoints[i], controlPoints[i + 1]).DistanceToPoint(position);
float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position);
if (dist < minDistance)
{
@ -115,20 +107,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
}
// Move the control points from the insertion index onwards to make room for the insertion
Array.Copy(controlPoints, insertionIndex, controlPoints, insertionIndex + 1, controlPoints.Length - insertionIndex - 1);
controlPoints[insertionIndex] = position;
onNewControlPoints(controlPoints);
HitObject.Path.ControlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } });
return insertionIndex;
}
private void onNewControlPoints(Vector2[] controlPoints)
{
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
HitObject.Path.ExpectedDistance.Value = null;
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.Distance) ?? (float)HitObject.Path.Distance;
UpdateHitObject();
}

View File

@ -28,11 +28,8 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
var newControlPoints = new Vector2[slider.Path.ControlPoints.Length];
for (int i = 0; i < slider.Path.ControlPoints.Length; i++)
newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y);
slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance);
foreach (var point in slider.Path.ControlPoints)
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
}
}
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
[Resolved(CanBeNull = true)]
private OsuRulesetConfigManager config { get; set; }
@ -84,9 +84,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable);
pathBindable.BindTo(slider.PathBindable);
pathVersion.BindTo(slider.Path.Version);
pathBindable.BindValueChanged(_ => Body.Refresh());
pathVersion.BindValueChanged(_ => Body.Refresh());
AccentColour.BindValueChanged(colour =>
{

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public class DrawableSliderHead : DrawableHitCircle
{
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
private readonly Slider slider;
@ -27,10 +27,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private void load()
{
positionBindable.BindTo(HitObject.PositionBindable);
pathBindable.BindTo(slider.PathBindable);
pathVersion.BindTo(slider.Path.Version);
positionBindable.BindValueChanged(_ => updatePosition());
pathBindable.BindValueChanged(_ => updatePosition(), true);
pathVersion.BindValueChanged(_ => updatePosition(), true);
}
protected override void Update()

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public bool Tracking { get; set; }
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle)
@ -36,10 +36,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true;
positionBindable.BindTo(hitCircle.PositionBindable);
pathBindable.BindTo(slider.PathBindable);
pathVersion.BindTo(slider.Path.Version);
positionBindable.BindValueChanged(_ => updatePosition());
pathBindable.BindValueChanged(_ => updatePosition(), true);
pathVersion.BindValueChanged(_ => updatePosition(), true);
// TODO: This has no drawable content. Support for skins should be added.
}

View File

@ -28,19 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
public readonly Bindable<SliderPath> PathBindable = new Bindable<SliderPath>();
public SliderPath Path
{
get => PathBindable.Value;
set
{
PathBindable.Value = value;
endPositionCache.Invalidate();
updateNestedPositions();
}
}
public SliderPath Path { get; set; } = new SliderPath(new[] { new PathControlPoint { Type = { Value = PathType.Bezier } } });
public double Distance => Path.Distance;

View File

@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
public class SliderTailCircle : SliderCircle
{
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
public SliderTailCircle(Slider slider)
{
pathBindable.BindTo(slider.PathBindable);
pathBindable.BindValueChanged(_ => Position = slider.EndPosition);
pathVersion.BindTo(slider.Path.Version);
pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
List<IList<HitSampleInfo>> nodeSamples)
{
newCombo |= forceNewCombo;
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch
X = position.X,
NewCombo = FirstObject || newCombo,
ComboOffset = comboOffset,
Path = new SliderPath(pathType, controlPoints, length),
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};

View File

@ -115,12 +115,6 @@ namespace osu.Game.Rulesets.Objects.Legacy
points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos;
}
// osu-stable special-cased colinear perfect curves to a CurveType.Linear
static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points))
pathType = PathType.Linear;
int repeatCount = Parsing.ParseInt(split[6]);
if (repeatCount > 9000)
@ -187,7 +181,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples);
result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples);
// The samples are played when the slider ends, which is the last node
result.Samples = nodeSamples[nodeSamples.Count - 1];
@ -259,6 +253,45 @@ namespace osu.Game.Rulesets.Objects.Legacy
bankInfo.Filename = split.Length > 4 ? split[4] : null;
}
private PathControlPoint[] convertControlPoints(Vector2[] vertices, PathType type)
{
if (type == PathType.PerfectCurve)
{
if (vertices.Length == 3)
{
// osu-stable special-cased colinear perfect curves to a linear path
if (isLinear(vertices))
type = PathType.Linear;
}
else
type = PathType.Bezier;
}
var points = new List<PathControlPoint>(vertices.Length)
{
new PathControlPoint
{
Position = { Value = vertices[0] },
Type = { Value = type }
}
};
for (int i = 1; i < vertices.Length; i++)
{
if (vertices[i] == vertices[i - 1])
{
points[points.Count - 1].Type.Value = type;
continue;
}
points.Add(new PathControlPoint { Position = { Value = vertices[i] } });
}
return points.ToArray();
static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
}
/// <summary>
/// Creates a legacy Hit-type hit object.
/// </summary>
@ -276,11 +309,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// <param name="comboOffset">When starting a new combo, the offset of the new combo relative to the current one.</param>
/// <param name="controlPoints">The slider control points.</param>
/// <param name="length">The slider length.</param>
/// <param name="pathType">The slider curve type.</param>
/// <param name="repeatCount">The slider repeat count.</param>
/// <param name="nodeSamples">The samples to be played when the slider nodes are hit. This includes the head and tail of the slider.</param>
/// <returns>The hit object.</returns>
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
List<IList<HitSampleInfo>> nodeSamples);
/// <summary>

View File

@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
List<IList<HitSampleInfo>> nodeSamples)
{
return new ConvertSlider
{
X = position.X,
Path = new SliderPath(pathType, controlPoints, length),
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
};
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
List<IList<HitSampleInfo>> nodeSamples)
{
newCombo |= forceNewCombo;
@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
Position = position,
NewCombo = FirstObject || newCombo,
ComboOffset = comboOffset,
Path = new SliderPath(pathType, controlPoints, length),
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};

View File

@ -23,12 +23,12 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
return new ConvertHit();
}
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double? length, PathType pathType, int repeatCount,
protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, PathControlPoint[] controlPoints, double? length, int repeatCount,
List<IList<HitSampleInfo>> nodeSamples)
{
return new ConvertSlider
{
Path = new SliderPath(pathType, controlPoints, length),
Path = new SliderPath(controlPoints, length),
NodeSamples = nodeSamples,
RepeatCount = repeatCount
};