Merge pull request #13535 from peppy/fix-hitcircle-approach-circle-early-hit

Fix approach circle fade not running early on an early user hit
This commit is contained in:
Dan Balasescu 2021-06-17 16:37:11 +09:00 committed by GitHub
commit a72151d4e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 19 deletions

View File

@ -21,20 +21,37 @@ namespace osu.Game.Rulesets.Osu.Tests
private int depthIndex; private int depthIndex;
[Test] [Test]
public void TestVariousHitCircles() public void TestHits()
{
AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true)));
AddStep("Hit Medium Single", () => SetContents(_ => testSingle(5, true)));
AddStep("Hit Small Single", () => SetContents(_ => testSingle(7, true)));
AddStep("Hit Big Stream", () => SetContents(_ => testStream(2, true)));
AddStep("Hit Medium Stream", () => SetContents(_ => testStream(5, true)));
AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true)));
}
[Test]
public void TestHittingEarly()
{
AddStep("Hit stream early", () => SetContents(_ => testStream(5, true, -150)));
}
[Test]
public void TestMisses()
{ {
AddStep("Miss Big Single", () => SetContents(_ => testSingle(2))); AddStep("Miss Big Single", () => SetContents(_ => testSingle(2)));
AddStep("Miss Medium Single", () => SetContents(_ => testSingle(5))); AddStep("Miss Medium Single", () => SetContents(_ => testSingle(5)));
AddStep("Miss Small Single", () => SetContents(_ => testSingle(7))); AddStep("Miss Small Single", () => SetContents(_ => testSingle(7)));
AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true)));
AddStep("Hit Medium Single", () => SetContents(_ => testSingle(5, true)));
AddStep("Hit Small Single", () => SetContents(_ => testSingle(7, true)));
AddStep("Miss Big Stream", () => SetContents(_ => testStream(2))); AddStep("Miss Big Stream", () => SetContents(_ => testStream(2)));
AddStep("Miss Medium Stream", () => SetContents(_ => testStream(5))); AddStep("Miss Medium Stream", () => SetContents(_ => testStream(5)));
AddStep("Miss Small Stream", () => SetContents(_ => testStream(7))); AddStep("Miss Small Stream", () => SetContents(_ => testStream(7)));
AddStep("Hit Big Stream", () => SetContents(_ => testStream(2, true))); }
AddStep("Hit Medium Stream", () => SetContents(_ => testStream(5, true)));
AddStep("Hit Small Stream", () => SetContents(_ => testStream(7, true))); [Test]
public void TestHittingLate()
{
AddStep("Hit stream late", () => SetContents(_ => testStream(5, true, 150)));
} }
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null) private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
@ -46,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests
return playfield; return playfield;
} }
private Drawable testStream(float circleSize, bool auto = false) private Drawable testStream(float circleSize, bool auto = false, double hitOffset = 0)
{ {
var playfield = new TestOsuPlayfield(); var playfield = new TestOsuPlayfield();
@ -54,14 +71,14 @@ namespace osu.Game.Rulesets.Osu.Tests
for (int i = 0; i <= 1000; i += 100) for (int i = 0; i <= 1000; i += 100)
{ {
playfield.Add(createSingle(circleSize, auto, i, pos)); playfield.Add(createSingle(circleSize, auto, i, pos, hitOffset));
pos.X += 50; pos.X += 50;
} }
return playfield; return playfield;
} }
private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset) private TestDrawableHitCircle createSingle(float circleSize, bool auto, double timeOffset, Vector2? positionOffset, double hitOffset = 0)
{ {
positionOffset ??= Vector2.Zero; positionOffset ??= Vector2.Zero;
@ -73,14 +90,14 @@ namespace osu.Game.Rulesets.Osu.Tests
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize }); circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
var drawable = CreateDrawableHitCircle(circle, auto); var drawable = CreateDrawableHitCircle(circle, auto, hitOffset);
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>()) foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable }); mod.ApplyToDrawableHitObjects(new[] { drawable });
return drawable; return drawable;
} }
protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto) protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0) => new TestDrawableHitCircle(circle, auto, hitOffset)
{ {
Depth = depthIndex++ Depth = depthIndex++
}; };
@ -88,18 +105,20 @@ namespace osu.Game.Rulesets.Osu.Tests
protected class TestDrawableHitCircle : DrawableHitCircle protected class TestDrawableHitCircle : DrawableHitCircle
{ {
private readonly bool auto; private readonly bool auto;
private readonly double hitOffset;
public TestDrawableHitCircle(HitCircle h, bool auto) public TestDrawableHitCircle(HitCircle h, bool auto, double hitOffset)
: base(h) : base(h)
{ {
this.auto = auto; this.auto = auto;
this.hitOffset = hitOffset;
} }
public void TriggerJudgement() => UpdateResult(true); public void TriggerJudgement() => Schedule(() => UpdateResult(true));
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (auto && !userTriggered && timeOffset > 0) if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current) != false)
{ {
// force success // force success
ApplyResult(r => r.Type = HitResult.Great); ApplyResult(r => r.Type = HitResult.Great);

View File

@ -16,11 +16,11 @@ namespace osu.Game.Rulesets.Osu.Tests
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true); Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
} }
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0)
{ {
circle.ComboIndexBindable.BindTo(comboIndex); circle.ComboIndexBindable.BindTo(comboIndex);
circle.IndexInCurrentComboBindable.BindTo(comboIndex); circle.IndexInCurrentComboBindable.BindTo(comboIndex);
return base.CreateDrawableHitCircle(circle, auto); return base.CreateDrawableHitCircle(circle, auto, hitOffset);
} }
} }
} }

View File

@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Osu.Tests
return base.CreateBeatmapForSkinProvider(); return base.CreateBeatmapForSkinProvider();
} }
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto, double hitOffset = 0)
{ {
var drawableHitObject = base.CreateDrawableHitCircle(circle, auto); var drawableHitObject = base.CreateDrawableHitCircle(circle, auto, hitOffset);
Debug.Assert(drawableHitObject.HitObject.HitWindows != null); Debug.Assert(drawableHitObject.HitObject.HitWindows != null);

View File

@ -172,6 +172,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
base.UpdateStartTimeStateTransforms(); base.UpdateStartTimeStateTransforms();
// always fade out at the circle's start time (to match user expectations).
ApproachCircle.FadeOut(50); ApproachCircle.FadeOut(50);
} }
@ -182,6 +183,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
// todo: temporary / arbitrary, used for lifetime optimisation. // todo: temporary / arbitrary, used for lifetime optimisation.
this.Delay(800).FadeOut(); this.Delay(800).FadeOut();
// in the case of an early state change, the fade should be expedited to the current point in time.
if (HitStateUpdateTime < HitObject.StartTime)
ApproachCircle.FadeOut(50);
switch (state) switch (state)
{ {
case ArmedState.Idle: case ArmedState.Idle: