1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 18:23:04 +08:00

Add SpinFramesGenerator class to simplify creating spinner tests

This commit is contained in:
Dean Herbert 2023-10-16 18:25:40 +09:00
parent 159b24acf7
commit cfa4adb24d
No known key found for this signature in database
4 changed files with 130 additions and 37 deletions

View File

@ -0,0 +1,111 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class SpinFramesGenerator
{
/// <summary>
/// A small amount to spin beyond a given angle to mitigate floating-point precision errors.
/// </summary>
public const float SPIN_ERROR = MathF.PI / 8;
/// <summary>
/// The offset from the centre of the spinner at which to spin.
/// </summary>
private const float centre_spin_offset = 50;
private readonly double startTime;
private readonly float startAngle;
private readonly List<(float deltaAngle, double duration)> sequences = new List<(float deltaAngle, double duration)>();
/// <summary>
/// Creates a new <see cref="SpinFramesGenerator"/> that can be used to generate spinner spin frames.
/// </summary>
/// <param name="startTime">The time at which to start spinning.</param>
/// <param name="startAngle">The angle, in radians, at which to start spinning from. Defaults to the positive-y-axis.</param>
public SpinFramesGenerator(double startTime, float startAngle = -MathF.PI / 2f)
{
this.startTime = startTime;
this.startAngle = startAngle;
}
/// <summary>
/// Performs a single spin.
/// </summary>
/// <param name="delta">The amount, relative to a full circle, to spin.</param>
/// <param name="duration">The time to spend to perform the spin.</param>
/// <returns>This <see cref="SpinFramesGenerator"/>.</returns>
public SpinFramesGenerator Spin(float delta, double duration)
{
sequences.Add((delta * 2 * MathF.PI, duration));
return this;
}
/// <summary>
/// Constructs the replay frames.
/// </summary>
/// <returns>The replay frames.</returns>
public List<ReplayFrame> Build()
{
List<ReplayFrame> frames = new List<ReplayFrame>();
double lastTime = startTime;
float lastAngle = startAngle;
int lastDirection = 0;
for (int i = 0; i < sequences.Count; i++)
{
var seq = sequences[i];
int seqDirection = Math.Sign(seq.deltaAngle);
float seqError = SPIN_ERROR * seqDirection;
if (seqDirection == lastDirection)
{
// Spinning in the same direction, but the error was already added in the last rotation.
seqError = 0;
}
else if (lastDirection != 0)
{
// Spinning in a different direction, we need to account for the error of the start angle, so double it.
seqError *= 2;
}
double seqStartTime = lastTime;
double seqEndTime = lastTime + seq.duration;
float seqStartAngle = lastAngle;
float seqEndAngle = seqStartAngle + seq.deltaAngle + seqError;
// Intermediate spin frames.
for (; lastTime < seqEndTime; lastTime += 10)
frames.Add(new OsuReplayFrame(lastTime, calcOffsetAt((lastTime - seqStartTime) / (seqEndTime - seqStartTime), seqStartAngle, seqEndAngle), OsuAction.LeftButton));
// Final frame at the end of the current spin.
frames.Add(new OsuReplayFrame(lastTime, calcOffsetAt(1, seqStartAngle, seqEndAngle), OsuAction.LeftButton));
lastTime = seqEndTime;
lastAngle = seqEndAngle;
lastDirection = seqDirection;
}
// Key release frame.
if (frames.Count > 0)
frames.Add(new OsuReplayFrame(frames[^1].Time, ((OsuReplayFrame)frames[^1]).Position));
return frames;
}
private static Vector2 calcOffsetAt(double p, float startAngle, float endAngle)
{
float angle = startAngle + (endAngle - startAngle) * (float)p;
return new Vector2(256, 192) + centre_spin_offset * new Vector2(MathF.Cos(angle), MathF.Sin(angle));
}
}
}

View File

@ -356,15 +356,16 @@ namespace osu.Game.Rulesets.Osu.Tests
},
};
performTest(hitObjects, new List<ReplayFrame>
List<ReplayFrame> frames = new List<ReplayFrame>
{
new OsuReplayFrame { Time = time_spinner - 90, Position = positionCircle, Actions = { OsuAction.LeftButton } },
new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 40, Position = new Vector2(256, 212), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 50, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } },
});
};
frames.AddRange(new SpinFramesGenerator(time_spinner + 10)
.Spin(1, 500)
.Build());
performTest(hitObjects, frames);
addJudgementAssert(hitObjects[0], HitResult.Great);
addJudgementAssert(hitObjects[1], HitResult.Meh);

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@ -11,14 +10,12 @@ using osu.Game.Beatmaps;
using osu.Game.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
@ -59,26 +56,9 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("all max judgements", () => judgementResults.All(result => result.Type == result.Judgement.MaxResult));
}
private static List<ReplayFrame> generateReplay(int spins)
{
var replayFrames = new List<ReplayFrame>();
const int frames_per_spin = 30;
for (int i = 0; i < spins * frames_per_spin; ++i)
{
float totalProgress = i / (float)(spins * frames_per_spin);
float spinProgress = (i % frames_per_spin) / (float)frames_per_spin;
double time = time_spinner_start + (time_spinner_end - time_spinner_start) * totalProgress;
float posX = MathF.Cos(2 * MathF.PI * spinProgress);
float posY = MathF.Sin(2 * MathF.PI * spinProgress);
Vector2 finalPos = OsuPlayfield.BASE_SIZE / 2 + new Vector2(posX, posY) * 50;
replayFrames.Add(new OsuReplayFrame(time, finalPos, OsuAction.LeftButton));
}
return replayFrames;
}
private static List<ReplayFrame> generateReplay(int spins) => new SpinFramesGenerator(time_spinner_start)
.Spin(spins, time_spinner_end - time_spinner_start)
.Build();
private void performTest(List<ReplayFrame> frames)
{

View File

@ -284,15 +284,16 @@ namespace osu.Game.Rulesets.Osu.Tests
},
};
performTest(hitObjects, new List<ReplayFrame>
List<ReplayFrame> frames = new List<ReplayFrame>
{
new OsuReplayFrame { Time = time_spinner - 100, Position = positionCircle, Actions = { OsuAction.LeftButton } },
new OsuReplayFrame { Time = time_spinner + 10, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 20, Position = new Vector2(256, 172), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 30, Position = new Vector2(276, 192), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 40, Position = new Vector2(256, 212), Actions = { OsuAction.RightButton } },
new OsuReplayFrame { Time = time_spinner + 50, Position = new Vector2(236, 192), Actions = { OsuAction.RightButton } },
});
};
frames.AddRange(new SpinFramesGenerator(time_spinner + 10)
.Spin(1, 500)
.Build());
performTest(hitObjects, frames);
addJudgementAssert(hitObjects[0], HitResult.Great);
addJudgementAssert(hitObjects[1], HitResult.Great);