mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 16:12:54 +08:00
Make DrawableSlider contain the slider body
This commit is contained in:
parent
ce7d212c3c
commit
08bb25347c
@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
List<Vector2> curve = drawableSlider.Body.CurrentCurve;
|
List<Vector2> curve = drawableSlider.Body.CurrentCurve;
|
||||||
|
|
||||||
var positionOnCurve = isRepeatAtEnd ? end : start;
|
var positionOnCurve = isRepeatAtEnd ? end : start;
|
||||||
Position = positionOnCurve + drawableSlider.HitObject.StackOffset;
|
Position = positionOnCurve - curve[0] + drawableSlider.HitObject.StackOffset;
|
||||||
|
|
||||||
if (curve.Count < 2)
|
if (curve.Count < 2)
|
||||||
return;
|
return;
|
||||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
slider = s;
|
slider = s;
|
||||||
|
|
||||||
|
Position = s.StackedPosition;
|
||||||
|
|
||||||
DrawableSliderTail tail;
|
DrawableSliderTail tail;
|
||||||
Container<DrawableSliderTick> ticks;
|
Container<DrawableSliderTick> ticks;
|
||||||
Container<DrawableRepeatPoint> repeatPoints;
|
Container<DrawableRepeatPoint> repeatPoints;
|
||||||
@ -39,20 +41,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Body = new SliderBody(s)
|
Body = new SliderBody(s)
|
||||||
{
|
{
|
||||||
AccentColour = AccentColour,
|
AccentColour = AccentColour,
|
||||||
Position = s.StackedPosition,
|
|
||||||
PathWidth = s.Scale * 64,
|
PathWidth = s.Scale * 64,
|
||||||
},
|
},
|
||||||
ticks = new Container<DrawableSliderTick>(),
|
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||||
repeatPoints = new Container<DrawableRepeatPoint>(),
|
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
||||||
Ball = new SliderBall(s)
|
Ball = new SliderBall(s)
|
||||||
{
|
{
|
||||||
|
BypassAutoSizeAxes = Axes.Both,
|
||||||
Scale = new Vector2(s.Scale),
|
Scale = new Vector2(s.Scale),
|
||||||
AccentColour = AccentColour,
|
AccentColour = AccentColour,
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
},
|
},
|
||||||
HeadCircle = new DrawableHitCircle(s.HeadCircle),
|
HeadCircle = new DrawableHitCircle(s.HeadCircle) { Position = s.HeadCircle.StackedPosition },
|
||||||
tail = new DrawableSliderTail(s.TailCircle)
|
tail = new DrawableSliderTail(s.TailCircle) { Position = s.TailCircle.StackedPosition }
|
||||||
};
|
};
|
||||||
|
|
||||||
components.Add(Body);
|
components.Add(Body);
|
||||||
@ -112,6 +114,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
|
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
|
||||||
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
|
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
|
||||||
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
|
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
|
||||||
|
|
||||||
|
Size = Body.Size;
|
||||||
|
OriginPosition = Body.PathOffset;
|
||||||
|
|
||||||
|
foreach (var obj in NestedHitObjects)
|
||||||
|
obj.RelativeAnchorPosition = Vector2.Divide(OriginPosition, Body.DrawSize);
|
||||||
|
Ball.RelativeAnchorPosition = Vector2.Divide(OriginPosition, Body.DrawSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||||
|
@ -19,8 +19,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public DrawableSliderTail(HitCircle hitCircle)
|
public DrawableSliderTail(HitCircle hitCircle)
|
||||||
: base(hitCircle)
|
: base(hitCircle)
|
||||||
{
|
{
|
||||||
AlwaysPresent = true;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
FillMode = FillMode.Fit;
|
||||||
|
|
||||||
|
AlwaysPresent = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||||
|
@ -29,6 +29,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
set { path.PathWidth = value; }
|
set { path.PathWidth = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Offset in absolute coordinates from the start of the curve.
|
||||||
|
/// </summary>
|
||||||
|
public Vector2 PathOffset { get; private set; }
|
||||||
|
|
||||||
|
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
|
||||||
|
|
||||||
public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
|
public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
|
||||||
public readonly Bindable<bool> SnakingOut = new Bindable<bool>();
|
public readonly Bindable<bool> SnakingOut = new Bindable<bool>();
|
||||||
|
|
||||||
@ -75,6 +82,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
private int textureWidth => (int)PathWidth * 2;
|
private int textureWidth => (int)PathWidth * 2;
|
||||||
|
|
||||||
|
private Vector2 topLeftOffset;
|
||||||
|
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
public SliderBody(Slider s)
|
public SliderBody(Slider s)
|
||||||
{
|
{
|
||||||
@ -84,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
{
|
{
|
||||||
container = new BufferedContainer
|
container = new BufferedContainer
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
CacheDrawnFrameBuffer = true,
|
CacheDrawnFrameBuffer = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -107,11 +117,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
if (updateSnaking(p0, p1))
|
if (updateSnaking(p0, p1))
|
||||||
{
|
{
|
||||||
// Autosizing does not give us the desired behaviour here.
|
// The path is generated such that its size encloses it. This change of size causes the path
|
||||||
// We want the container to have the same size as the slider,
|
// to move around while snaking, so we need to offset it to make sure it maintains the
|
||||||
// and to be positioned such that the slider head is at (0,0).
|
// same position as when it is fully snaked.
|
||||||
container.Size = path.Size;
|
var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
container.Position = -path.PositionInBoundingBox(slider.Curve.PositionAt(0) - CurrentCurve[0]);
|
path.Position = topLeftOffset - newTopLeftOffset;
|
||||||
|
|
||||||
container.ForceRedraw();
|
container.ForceRedraw();
|
||||||
}
|
}
|
||||||
@ -121,6 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
reloadTexture();
|
reloadTexture();
|
||||||
|
computeSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reloadTexture()
|
private void reloadTexture()
|
||||||
@ -164,7 +175,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
path.Texture = texture;
|
path.Texture = texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
|
private void computeSize()
|
||||||
|
{
|
||||||
|
// Generate the entire curve
|
||||||
|
slider.Curve.GetPathToProgress(CurrentCurve, 0, 1);
|
||||||
|
foreach (Vector2 p in CurrentCurve)
|
||||||
|
path.AddVertex(p);
|
||||||
|
|
||||||
|
Size = path.Size;
|
||||||
|
|
||||||
|
topLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
|
PathOffset = path.PositionInBoundingBox(CurrentCurve[0]);
|
||||||
|
}
|
||||||
|
|
||||||
private bool updateSnaking(double p0, double p1)
|
private bool updateSnaking(double p0, double p1)
|
||||||
{
|
{
|
||||||
if (SnakedStart == p0 && SnakedEnd == p1) return false;
|
if (SnakedStart == p0 && SnakedEnd == p1) return false;
|
||||||
@ -176,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
|
|
||||||
path.ClearVertices();
|
path.ClearVertices();
|
||||||
foreach (Vector2 p in CurrentCurve)
|
foreach (Vector2 p in CurrentCurve)
|
||||||
path.AddVertex(p - CurrentCurve[0]);
|
path.AddVertex(p);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
HeadCircle = new HitCircle
|
HeadCircle = new HitCircle
|
||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
Position = StackedPosition,
|
Position = this.PositionAt(0),
|
||||||
IndexInCurrentCombo = IndexInCurrentCombo,
|
IndexInCurrentCombo = IndexInCurrentCombo,
|
||||||
ComboColour = ComboColour,
|
ComboColour = ComboColour,
|
||||||
Samples = Samples,
|
Samples = Samples,
|
||||||
@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
TailCircle = new HitCircle
|
TailCircle = new HitCircle
|
||||||
{
|
{
|
||||||
StartTime = EndTime,
|
StartTime = EndTime,
|
||||||
Position = StackedEndPosition,
|
Position = this.PositionAt(1),
|
||||||
IndexInCurrentCombo = IndexInCurrentCombo,
|
IndexInCurrentCombo = IndexInCurrentCombo,
|
||||||
ComboColour = ComboColour
|
ComboColour = ComboColour
|
||||||
};
|
};
|
||||||
@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
SpanIndex = span,
|
SpanIndex = span,
|
||||||
SpanStartTime = spanStartTime,
|
SpanStartTime = spanStartTime,
|
||||||
StartTime = spanStartTime + timeProgress * SpanDuration,
|
StartTime = spanStartTime + timeProgress * SpanDuration,
|
||||||
Position = Curve.PositionAt(distanceProgress),
|
Position = Curve.PositionAt(distanceProgress) - Curve.PositionAt(0),
|
||||||
StackHeight = StackHeight,
|
StackHeight = StackHeight,
|
||||||
Scale = Scale,
|
Scale = Scale,
|
||||||
ComboColour = ComboColour,
|
ComboColour = ComboColour,
|
||||||
@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
RepeatIndex = repeatIndex,
|
RepeatIndex = repeatIndex,
|
||||||
SpanDuration = SpanDuration,
|
SpanDuration = SpanDuration,
|
||||||
StartTime = StartTime + repeat * SpanDuration,
|
StartTime = StartTime + repeat * SpanDuration,
|
||||||
Position = Curve.PositionAt(repeat % 2),
|
Position = Curve.PositionAt(repeat % 2) - Curve.PositionAt(0),
|
||||||
StackHeight = StackHeight,
|
StackHeight = StackHeight,
|
||||||
Scale = Scale,
|
Scale = Scale,
|
||||||
ComboColour = ComboColour,
|
ComboColour = ComboColour,
|
||||||
@ -184,4 +184,10 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class SliderExtensions
|
||||||
|
{
|
||||||
|
public static Vector2 PositionAt(this Slider slider, double progress)
|
||||||
|
=> ((IHasCurve)slider).PositionAt(progress) - slider.Curve.PositionAt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
171
osu.Game.Rulesets.Osu/Tests/TestCaseNewSliderBody.cs
Normal file
171
osu.Game.Rulesets.Osu/Tests/TestCaseNewSliderBody.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (c) 2007-2018 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Lines;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestCaseNewSliderBody : OsuTestCase
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Path) };
|
||||||
|
|
||||||
|
private readonly NewSliderBody body;
|
||||||
|
|
||||||
|
public TestCaseNewSliderBody()
|
||||||
|
{
|
||||||
|
Add(body = new NewSliderBody(new SliderCurve
|
||||||
|
{
|
||||||
|
ControlPoints = new List<Vector2>
|
||||||
|
{
|
||||||
|
new Vector2(-200, 0),
|
||||||
|
new Vector2(-50, 75),
|
||||||
|
new Vector2(0, 100),
|
||||||
|
new Vector2(100, -200),
|
||||||
|
new Vector2(230, 0)
|
||||||
|
},
|
||||||
|
Distance = 480,
|
||||||
|
CurveType = CurveType.Bezier
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre
|
||||||
|
});
|
||||||
|
|
||||||
|
AddSliderStep("In", 0f, 1f, 0f, v => inLength = v);
|
||||||
|
AddSliderStep("Out", 0f, 1f, 1f, v => outLength = v);
|
||||||
|
AddSliderStep("Path Width", 0f, 100f, 10f, v => body.PathWidth = v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _inLength;
|
||||||
|
|
||||||
|
private float inLength
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_inLength = value;
|
||||||
|
body.UpdateSnaking(_inLength, _outLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float _outLength;
|
||||||
|
|
||||||
|
private float outLength
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_outLength = value;
|
||||||
|
body.UpdateSnaking(_inLength, _outLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NewSliderBody : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Path path;
|
||||||
|
private readonly SliderCurve curve;
|
||||||
|
|
||||||
|
public NewSliderBody(SliderCurve curve)
|
||||||
|
{
|
||||||
|
this.curve = curve;
|
||||||
|
|
||||||
|
InternalChild = path = new Path();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
reloadTexture();
|
||||||
|
computeSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public float PathWidth
|
||||||
|
{
|
||||||
|
get => path.PathWidth;
|
||||||
|
set { path.PathWidth = value; reloadTexture(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadTexture()
|
||||||
|
{
|
||||||
|
var textureWidth = (int)PathWidth * 2;
|
||||||
|
|
||||||
|
//initialise background
|
||||||
|
var texture = new Texture(textureWidth, 1);
|
||||||
|
var upload = new TextureUpload(textureWidth * 4);
|
||||||
|
var bytes = upload.Data;
|
||||||
|
|
||||||
|
const float aa_portion = 0.02f;
|
||||||
|
const float border_portion = 0.128f;
|
||||||
|
const float gradient_portion = 1 - border_portion;
|
||||||
|
|
||||||
|
const float opacity_at_centre = 0.3f;
|
||||||
|
const float opacity_at_edge = 0.8f;
|
||||||
|
|
||||||
|
for (int i = 0; i < textureWidth; i++)
|
||||||
|
{
|
||||||
|
float progress = (float)i / (textureWidth - 1);
|
||||||
|
|
||||||
|
if (progress <= border_portion)
|
||||||
|
{
|
||||||
|
bytes[i * 4] = (byte)(Color4.White.R * 255);
|
||||||
|
bytes[i * 4 + 1] = (byte)(Color4.White.G * 255);
|
||||||
|
bytes[i * 4 + 2] = (byte)(Color4.White.B * 255);
|
||||||
|
bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (Color4.White.A * 255));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
progress -= border_portion;
|
||||||
|
|
||||||
|
bytes[i * 4] = (byte)(Color4.Blue.R * 255);
|
||||||
|
bytes[i * 4 + 1] = (byte)(Color4.Blue.G * 255);
|
||||||
|
bytes[i * 4 + 2] = (byte)(Color4.Blue.B * 255);
|
||||||
|
bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (Color4.Blue.A * 255));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
texture.SetData(upload);
|
||||||
|
path.Texture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector2 topLeftOffset;
|
||||||
|
|
||||||
|
private void computeSize()
|
||||||
|
{
|
||||||
|
// Compute the final size
|
||||||
|
var fullPath = new List<Vector2>();
|
||||||
|
curve.GetPathToProgress(fullPath, 0, 1);
|
||||||
|
|
||||||
|
foreach (Vector2 p in fullPath)
|
||||||
|
path.AddVertex(p);
|
||||||
|
|
||||||
|
Size = path.Size;
|
||||||
|
|
||||||
|
topLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
|
OriginPosition = path.PositionInBoundingBox(fullPath[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSnaking(float t0, float t1)
|
||||||
|
{
|
||||||
|
var curvePath = new List<Vector2>();
|
||||||
|
curve.GetPathToProgress(curvePath, t0, t1);
|
||||||
|
|
||||||
|
path.ClearVertices();
|
||||||
|
foreach (Vector2 p in curvePath)
|
||||||
|
path.AddVertex(p);
|
||||||
|
|
||||||
|
var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
|
path.Position = topLeftOffset - newTopLeftOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -124,6 +124,7 @@
|
|||||||
<Compile Include="Replays\OsuReplayInputHandler.cs" />
|
<Compile Include="Replays\OsuReplayInputHandler.cs" />
|
||||||
<Compile Include="Tests\TestCaseHitCircle.cs" />
|
<Compile Include="Tests\TestCaseHitCircle.cs" />
|
||||||
<Compile Include="Tests\TestCaseHitCircleHidden.cs" />
|
<Compile Include="Tests\TestCaseHitCircleHidden.cs" />
|
||||||
|
<Compile Include="Tests\TestCaseNewSliderBody.cs" />
|
||||||
<Compile Include="Tests\TestCasePerformancePoints.cs" />
|
<Compile Include="Tests\TestCasePerformancePoints.cs" />
|
||||||
<Compile Include="Tests\TestCaseSlider.cs" />
|
<Compile Include="Tests\TestCaseSlider.cs" />
|
||||||
<Compile Include="Tests\TestCaseSliderHidden.cs" />
|
<Compile Include="Tests\TestCaseSliderHidden.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user