mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 11:42:54 +08:00
Add scrolling gameplay visualisation mode to latency certifier
This commit is contained in:
parent
fd09760421
commit
f997886142
@ -36,6 +36,12 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("set visual mode to circles", () => latencyCertifier.VisualMode.Value = LatencyVisualMode.CircleGameplay);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollingGameplay()
|
||||
{
|
||||
AddStep("set visual mode to scrolling", () => latencyCertifier.VisualMode.Value = LatencyVisualMode.ScrollingGameplay);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCycleVisualModes()
|
||||
{
|
||||
|
@ -108,6 +108,20 @@ namespace osu.Game.Screens.Utility
|
||||
};
|
||||
break;
|
||||
|
||||
case LatencyVisualMode.ScrollingGameplay:
|
||||
visualContent.Children = new Drawable[]
|
||||
{
|
||||
new ScrollingGameplay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new LatencyCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ namespace osu.Game.Screens.Utility
|
||||
public enum LatencyVisualMode
|
||||
{
|
||||
CircleGameplay,
|
||||
ScrollingGameplay,
|
||||
Simple,
|
||||
}
|
||||
}
|
||||
|
201
osu.Game/Screens/Utility/ScrollingGameplay.cs
Normal file
201
osu.Game/Screens/Utility/ScrollingGameplay.cs
Normal file
@ -0,0 +1,201 @@
|
||||
// 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.
|
||||
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Utility.SampleComponents;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Utility
|
||||
{
|
||||
public class ScrollingGameplay : LatencySampleComponent
|
||||
{
|
||||
private const float judgement_position = 0.8f;
|
||||
private const float bar_height = 20;
|
||||
|
||||
private int nextLocation;
|
||||
|
||||
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
|
||||
|
||||
private double? lastGeneratedBeatTime;
|
||||
|
||||
private Container circles = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Name = "judgement bar",
|
||||
Colour = OverlayColourProvider.Content2,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
Y = judgement_position,
|
||||
Height = bar_height,
|
||||
},
|
||||
circles = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
|
||||
SampleBPM.BindValueChanged(_ =>
|
||||
{
|
||||
circles.Clear();
|
||||
lastGeneratedBeatTime = null;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateAtLimitedRate(InputState inputState)
|
||||
{
|
||||
double beatLength = 60000 / SampleBPM.Value;
|
||||
|
||||
int nextBeat = (int)(Clock.CurrentTime / beatLength) + 1;
|
||||
|
||||
// We want to generate a few hit objects ahead of the current time (to allow them to animate).
|
||||
double generateUpTo = (nextBeat + 2) * beatLength;
|
||||
|
||||
while (lastGeneratedBeatTime == null || lastGeneratedBeatTime < generateUpTo)
|
||||
{
|
||||
double time = ++nextBeat * beatLength;
|
||||
|
||||
if (time <= lastGeneratedBeatTime)
|
||||
continue;
|
||||
|
||||
newBeat(time);
|
||||
lastGeneratedBeatTime = time;
|
||||
}
|
||||
}
|
||||
|
||||
private void newBeat(double time)
|
||||
{
|
||||
const float columns = 4;
|
||||
|
||||
float adjustedXPos = ((1f + nextLocation++ % columns) - columns / 2) / columns;
|
||||
|
||||
circles.Add(new SampleNote(time)
|
||||
{
|
||||
RelativePositionAxes = Axes.Both,
|
||||
X = 0.5f + SampleVisualSpacing.Value * (adjustedXPos * 0.5f),
|
||||
Scale = new Vector2(0.4f + (0.8f * SampleVisualSpacing.Value), 1),
|
||||
Hit = hit,
|
||||
});
|
||||
}
|
||||
|
||||
private void hit(HitEvent h)
|
||||
{
|
||||
hitEvents.Add(h);
|
||||
}
|
||||
|
||||
public class SampleNote : LatencySampleComponent
|
||||
{
|
||||
public HitEvent? HitEvent;
|
||||
|
||||
public Action<HitEvent>? Hit { get; set; }
|
||||
|
||||
public readonly double HitTime;
|
||||
|
||||
private Box box = null!;
|
||||
|
||||
private const float size = 100;
|
||||
private const float duration = 200;
|
||||
|
||||
public SampleNote(double hitTime)
|
||||
{
|
||||
HitTime = hitTime;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
AlwaysPresent = true;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Size = new Vector2(size, bar_height),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (!IsActive.Value)
|
||||
return false;
|
||||
|
||||
if (Math.Abs(Clock.CurrentTime - HitTime) > duration)
|
||||
return false;
|
||||
|
||||
// Allow using any key that isn't used by the latency certifier itself.
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Space:
|
||||
case Key.Number1:
|
||||
case Key.Number2:
|
||||
case Key.Tab:
|
||||
return false;
|
||||
}
|
||||
|
||||
attemptHit();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void UpdateAtLimitedRate(InputState inputState)
|
||||
{
|
||||
if (HitEvent == null)
|
||||
{
|
||||
double preempt = (float)IBeatmapDifficultyInfo.DifficultyRange(SampleApproachRate.Value, 1800, 1200, 450);
|
||||
|
||||
Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1);
|
||||
Y = judgement_position - (float)((HitTime - Clock.CurrentTime) / preempt);
|
||||
|
||||
if (Clock.CurrentTime > HitTime + duration)
|
||||
Expire();
|
||||
}
|
||||
}
|
||||
|
||||
private void attemptHit() => Schedule(() =>
|
||||
{
|
||||
if (HitEvent != null)
|
||||
return;
|
||||
|
||||
// in case it was hit outside of display range, show immediately
|
||||
// so the user isn't confused.
|
||||
this.FadeIn();
|
||||
|
||||
box
|
||||
.FadeOut(duration / 2)
|
||||
.ScaleTo(1.5f, duration / 2);
|
||||
|
||||
HitEvent = new HitEvent(Clock.CurrentTime - HitTime, HitResult.Good, new HitObject
|
||||
{
|
||||
HitWindows = new HitWindows(),
|
||||
}, null, null);
|
||||
|
||||
Hit?.Invoke(HitEvent.Value);
|
||||
|
||||
this.Delay(duration).Expire();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user