Merge remote-tracking branch 'origin/master' into fix-slider-samples

# Conflicts:
#	osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
#	osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs
#	osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs
This commit is contained in:
smoogipoo
2018-11-05 16:32:27 +09:00
143 changed files with 3066 additions and 1068 deletions

View File

@ -61,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Size = circle.DrawSize;
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
HitObject.StackHeightChanged += _ => Position = HitObject.StackedPosition;
HitObject.ScaleChanged += s => Scale = new Vector2(s);
}
public override Color4 AccentColour

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly DrawableHitCircle HeadCircle;
public readonly DrawableSliderTail TailCircle;
public readonly SliderBody Body;
public readonly SnakingSliderBody Body;
public readonly SliderBall Ball;
public DrawableSlider(Slider s)
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
Body = new SliderBody(s)
Body = new SnakingSliderBody(s)
{
PathWidth = s.Scale * 64,
},
@ -85,6 +85,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
HitObject.ScaleChanged += _ =>
{
Body.PathWidth = HitObject.Scale * 64;
Ball.Scale = new Vector2(HitObject.Scale);
};
slider.ControlPointsChanged += _ => Body.Refresh();
}
public override Color4 AccentColour
@ -119,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
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.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
Size = Body.Size;

View File

@ -16,7 +16,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
this.slider = slider;
Position = HitObject.Position - slider.Position;
h.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
updatePosition();
}
protected override void Update()
@ -33,5 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Action<double> OnShake;
protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength);
private void updatePosition() => Position = HitObject.Position - slider.Position;
}
}

View File

@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking
{
private readonly Slider slider;
/// <summary>
/// The judgement text is provided by the <see cref="DrawableSlider"/>.
/// </summary>
@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle)
{
this.slider = slider;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
@ -25,7 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true;
Position = HitObject.Position - slider.Position;
hitCircle.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
updatePosition();
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@ -33,5 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
if (!userTriggered && timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
}
private void updatePosition() => Position = HitObject.Position - slider.Position;
}
}

View File

@ -112,6 +112,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Alpha = 0
}
};
s.PositionChanged += _ => Position = s.Position;
}
public float Progress => MathHelper.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1);
@ -167,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
Disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
if (!spmCounter.IsPresent && Disc.Tracking)
spmCounter.FadeIn(HitObject.TimeFadeIn);

View File

@ -0,0 +1,20 @@
// 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.Collections.Generic;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
/// <summary>
/// A <see cref="SliderBody"/> with the ability to set the drawn vertices manually.
/// </summary>
public class ManualSliderBody : SliderBody
{
public new void SetVertices(IReadOnlyList<Vector2> vertices)
{
base.SetVertices(vertices);
Size = Path.Size;
}
}
}

View File

@ -4,7 +4,6 @@
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class NumberPiece : Container
{
private readonly SpriteText number;
private readonly SkinnableSpriteText number;
public string Text
{
@ -41,15 +40,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
},
Child = new Box()
}, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new OsuSpriteText
number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText
{
Text = @"1",
Font = @"Venera",
UseFullGlyphHeight = false,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 40,
Alpha = 1
}, restrictSize: false)
{
Text = @"1"
}
};
}

View File

@ -1,24 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using OpenTK.Graphics.ES30;
using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SliderBody : Container, ISliderProgress
public abstract class SliderBody : CompositeDrawable
{
private readonly SliderPath path;
protected Path Path => path;
private readonly BufferedContainer container;
public float PathWidth
@ -30,15 +28,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// <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> SnakingOut = new Bindable<bool>();
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]);
/// <summary>
/// Used to colour the path.
@ -74,28 +64,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
private Vector2 topLeftOffset;
private readonly Slider slider;
public SliderBody(Slider s)
protected SliderBody()
{
slider = s;
Children = new Drawable[]
InternalChild = container = new BufferedContainer
{
container = new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
{
path = new SliderPath
{
Blending = BlendingMode.None,
},
}
},
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Child = path = new SliderPath { Blending = BlendingMode.None }
};
container.Attach(RenderbufferInternalFormat.DepthComponent16);
@ -103,80 +78,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
public void SetRange(double p0, double p1)
/// <summary>
/// Sets the vertices of the path which should be drawn by this <see cref="SliderBody"/>.
/// </summary>
/// <param name="vertices">The vertices</param>
protected void SetVertices(IReadOnlyList<Vector2> vertices)
{
if (p0 > p1)
MathHelper.Swap(ref p0, ref p1);
if (updateSnaking(p0, p1))
{
// The path is generated such that its size encloses it. This change of size causes the path
// to move around while snaking, so we need to offset it to make sure it maintains the
// same position as when it is fully snaked.
var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
path.Position = topLeftOffset - newTopLeftOffset;
container.ForceRedraw();
}
}
[BackgroundDependencyLoader]
private void load()
{
computeSize();
}
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)
{
if (SnakedStart == p0 && SnakedEnd == p1) return false;
SnakedStart = p0;
SnakedEnd = p1;
slider.Curve.GetPathToProgress(CurrentCurve, p0, p1);
path.ClearVertices();
foreach (Vector2 p in CurrentCurve)
path.AddVertex(p);
return true;
}
public void UpdateProgress(double completionProgress)
{
var span = slider.SpanAt(completionProgress);
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0;
double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
{
start = 0;
end = SnakingOut ? spanProgress : 1;
}
else
{
start = SnakingOut ? spanProgress : 0;
}
}
SetRange(start, end);
path.Vertices = vertices;
container.ForceRedraw();
}
private class SliderPath : SmoothPath

View File

@ -0,0 +1,118 @@
// 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.Configuration;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
/// <summary>
/// A <see cref="SliderBody"/> which changes its curve depending on the snaking progress.
/// </summary>
public class SnakingSliderBody : SliderBody, ISliderProgress
{
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
public readonly Bindable<bool> SnakingIn = new Bindable<bool>();
public readonly Bindable<bool> SnakingOut = new Bindable<bool>();
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
public override Vector2 PathOffset => snakedPathOffset;
/// <summary>
/// The top-left position of the path when fully snaked.
/// </summary>
private Vector2 snakedPosition;
/// <summary>
/// The offset of the path from <see cref="snakedPosition"/> when fully snaked.
/// </summary>
private Vector2 snakedPathOffset;
private readonly Slider slider;
public SnakingSliderBody(Slider slider)
{
this.slider = slider;
}
[BackgroundDependencyLoader]
private void load()
{
Refresh();
}
public void UpdateProgress(double completionProgress)
{
var span = slider.SpanAt(completionProgress);
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0;
double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
{
start = 0;
end = SnakingOut ? spanProgress : 1;
}
else
{
start = SnakingOut ? spanProgress : 0;
}
}
setRange(start, end);
}
public void Refresh()
{
// Generate the entire curve
slider.Path.GetPathToProgress(CurrentCurve, 0, 1);
SetVertices(CurrentCurve);
// The body is sized to the full path size to avoid excessive autosize computations
Size = Path.Size;
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
var lastSnakedStart = SnakedStart ?? 0;
var lastSnakedEnd = SnakedEnd ?? 0;
SnakedStart = null;
SnakedEnd = null;
setRange(lastSnakedStart, lastSnakedEnd);
}
private void setRange(double p0, double p1)
{
if (p0 > p1)
MathHelper.Swap(ref p0, ref p1);
if (SnakedStart == p0 && SnakedEnd == p1) return;
SnakedStart = p0;
SnakedEnd = p1;
slider.Path.GetPathToProgress(CurrentCurve, p0, p1);
SetVertices(CurrentCurve);
// The bounding box of the path expands as it snakes, which in turn shifts the position of the path.
// Depending on the direction of expansion, it may appear as if the path is expanding towards the position of the slider
// rather than expanding out from the position of the slider.
// To remove this effect, the path's position is shifted towards its final snaked position
Path.Position = snakedPosition - Path.PositionInBoundingBox(Vector2.Zero);
}
}
}

View File

@ -16,13 +16,15 @@ namespace osu.Game.Rulesets.Osu.Objects
public const double OBJECT_RADIUS = 64;
public event Action<Vector2> PositionChanged;
public event Action<int> StackHeightChanged;
public event Action<float> ScaleChanged;
public double TimePreempt = 600;
public double TimeFadeIn = 400;
private Vector2 position;
public Vector2 Position
public virtual Vector2 Position
{
get => position;
set
@ -44,13 +46,39 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedEndPosition => EndPosition + StackOffset;
public virtual int StackHeight { get; set; }
private int stackHeight;
public int StackHeight
{
get => stackHeight;
set
{
if (stackHeight == value)
return;
stackHeight = value;
StackHeightChanged?.Invoke(value);
}
}
public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
public double Radius => OBJECT_RADIUS * Scale;
public float Scale { get; set; } = 1;
private float scale = 1;
public float Scale
{
get => scale;
set
{
if (scale == value)
return;
scale = value;
ScaleChanged?.Invoke(value);
}
}
public virtual bool NewCombo { get; set; }

View File

@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
private const float base_scoring_distance = 100;
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public event Action<Vector2[]> ControlPointsChanged;
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime;
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
@ -50,24 +52,49 @@ namespace osu.Game.Rulesets.Osu.Objects
}
}
public SliderCurve Curve { get; } = new SliderCurve();
public SliderPath Path { get; } = new SliderPath();
public List<Vector2> ControlPoints
public Vector2[] ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
get => Path.ControlPoints;
set
{
if (Path.ControlPoints == value)
return;
Path.ControlPoints = value;
ControlPointsChanged?.Invoke(value);
if (TailCircle != null)
TailCircle.Position = EndPosition;
}
}
public CurveType CurveType
public PathType PathType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
get { return Path.PathType; }
set { Path.PathType = value; }
}
public double Distance
{
get { return Curve.Distance; }
set { Curve.Distance = value; }
get { return Path.Distance; }
set { Path.Distance = value; }
}
public override Vector2 Position
{
get => base.Position;
set
{
base.Position = value;
if (HeadCircle != null)
HeadCircle.Position = value;
if (TailCircle != null)
TailCircle.Position = EndPosition;
}
}
public double? LegacyLastTickOffset { get; set; }
@ -93,8 +120,21 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
public double SpanDuration => Duration / this.SpanCount();
public double Velocity;
public double TickDistance;
/// <summary>
/// Velocity of this <see cref="Slider"/>.
/// </summary>
public double Velocity { get; private set; }
/// <summary>
/// Spacing between <see cref="SliderTick"/>s of this <see cref="Slider"/>.
/// </summary>
public double TickDistance { get; private set; }
/// <summary>
/// An extra multiplier that affects the number of <see cref="SliderTick"/>s generated by this <see cref="Slider"/>.
/// An increase in this value increases <see cref="TickDistance"/>, which reduces the number of ticks generated.
/// </summary>
public double TickDistanceMultiplier = 1;
public HitCircle HeadCircle;
public SliderTailCircle TailCircle;
@ -109,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate;
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
}
protected override void CreateNestedHitObjects()
@ -150,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createTicks()
{
var length = Curve.Distance;
var length = Path.Distance;
var tickDistance = MathHelper.Clamp(TickDistance, 0, length);
if (tickDistance == 0) return;
@ -189,7 +229,7 @@ namespace osu.Game.Rulesets.Osu.Objects
SpanIndex = span,
SpanStartTime = spanStartTime,
StartTime = spanStartTime + timeProgress * SpanDuration,
Position = Position + Curve.PositionAt(distanceProgress),
Position = Position + Path.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
Samples = sampleList
@ -207,7 +247,7 @@ namespace osu.Game.Rulesets.Osu.Objects
RepeatIndex = repeatIndex,
SpanDuration = SpanDuration,
StartTime = StartTime + repeat * SpanDuration,
Position = Position + Curve.PositionAt(repeat % 2),
Position = Position + Path.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
Samples = getNodeSamples(1 + repeatIndex)

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -31,5 +32,10 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
public override void OffsetPosition(Vector2 offset)
{
// for now we don't want to allow spinners to be moved around.
}
}
}