From 53eb5c176c48fd86677f22a5cf09fe7575ce64e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 16:22:34 +0900 Subject: [PATCH 01/27] Create setup for adding other visual display modes --- osu.Game/Screens/Utility/CircleGameplay.cs | 12 + osu.Game/Screens/Utility/LatencyArea.cs | 206 +++++------------- .../Screens/Utility/LatencyCertifierScreen.cs | 16 +- osu.Game/Screens/Utility/LatencyVisualMode.cs | 13 ++ .../LatencyCursorContainer.cs | 67 ++++++ .../SampleComponents/LatencyMovableBox.cs | 102 +++++++++ osu.Game/Screens/Utility/ScrollingGameplay.cs | 12 + 7 files changed, 277 insertions(+), 151 deletions(-) create mode 100644 osu.Game/Screens/Utility/CircleGameplay.cs create mode 100644 osu.Game/Screens/Utility/LatencyVisualMode.cs create mode 100644 osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs create mode 100644 osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs create mode 100644 osu.Game/Screens/Utility/ScrollingGameplay.cs diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs new file mode 100644 index 0000000000..0667a54ced --- /dev/null +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Utility +{ + public class CircleGameplay : CompositeDrawable + { + } +} diff --git a/osu.Game/Screens/Utility/LatencyArea.cs b/osu.Game/Screens/Utility/LatencyArea.cs index 2ef48bb571..5b48a10a49 100644 --- a/osu.Game/Screens/Utility/LatencyArea.cs +++ b/osu.Game/Screens/Utility/LatencyArea.cs @@ -9,10 +9,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Overlays; -using osuTK; +using osu.Game.Screens.Utility.SampleComponents; using osuTK.Input; namespace osu.Game.Screens.Utility @@ -28,10 +27,14 @@ namespace osu.Game.Screens.Utility private readonly Key key; + private Container visualContent = null!; + public readonly int? TargetFrameRate; public readonly BindableBool IsActiveArea = new BindableBool(); + public readonly Bindable VisualMode = new Bindable(); + public LatencyArea(Key key, int? targetFrameRate) { this.key = key; @@ -61,20 +64,9 @@ namespace osu.Game.Screens.Utility Origin = Anchor.TopCentre, Action = () => ReportUserBest?.Invoke(), }, - new Container + visualContent = new Container { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new LatencyMovableBox(IsActiveArea) - { - RelativeSizeAxes = Axes.Both, - }, - new LatencyCursorContainer(IsActiveArea) - { - RelativeSizeAxes = Axes.Both, - }, - } }, }; @@ -82,6 +74,57 @@ namespace osu.Game.Screens.Utility { background.FadeColour(active.NewValue ? overlayColourProvider.Background4 : overlayColourProvider.Background6, 200, Easing.OutQuint); }, true); + + VisualMode.BindValueChanged(mode => + { + switch (mode.NewValue) + { + case LatencyVisualMode.Simple: + visualContent.Children = new Drawable[] + { + new LatencyMovableBox(IsActiveArea) + { + RelativeSizeAxes = Axes.Both, + }, + new LatencyCursorContainer(IsActiveArea) + { + RelativeSizeAxes = Axes.Both, + }, + }; + break; + + case LatencyVisualMode.CircleGameplay: + visualContent.Children = new Drawable[] + { + new CircleGameplay + { + RelativeSizeAxes = Axes.Both, + }, + new LatencyCursorContainer(IsActiveArea) + { + RelativeSizeAxes = Axes.Both, + }, + }; + break; + + case LatencyVisualMode.ScrollingGameplay: + visualContent.Children = new Drawable[] + { + new ScrollingGameplay + { + RelativeSizeAxes = Axes.Both, + }, + new LatencyCursorContainer(IsActiveArea) + { + RelativeSizeAxes = Axes.Both, + }, + }; + break; + + default: + throw new ArgumentOutOfRangeException(); + } + }, true); } protected override bool OnMouseMove(MouseMoveEvent e) @@ -102,140 +145,5 @@ namespace osu.Game.Screens.Utility return base.UpdateSubTree(); } - - public class LatencyMovableBox : CompositeDrawable - { - private Box box = null!; - private InputManager inputManager = null!; - - private readonly BindableBool isActive; - - [Resolved] - private OverlayColourProvider overlayColourProvider { get; set; } = null!; - - public LatencyMovableBox(BindableBool isActive) - { - this.isActive = isActive; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - - InternalChild = box = new Box - { - Size = new Vector2(40), - RelativePositionAxes = Axes.Both, - Position = new Vector2(0.5f), - Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour1, - }; - } - - protected override bool OnHover(HoverEvent e) => false; - - private double? lastFrameTime; - - protected override void Update() - { - base.Update(); - - if (!isActive.Value) - { - lastFrameTime = null; - box.Colour = overlayColourProvider.Colour1; - return; - } - - if (lastFrameTime != null) - { - float movementAmount = (float)(Clock.CurrentTime - lastFrameTime) / 400; - - var buttons = inputManager.CurrentState.Keyboard.Keys; - - box.Colour = buttons.HasAnyButtonPressed ? overlayColourProvider.Content1 : overlayColourProvider.Colour1; - - foreach (var key in buttons) - { - switch (key) - { - case Key.K: - case Key.Up: - box.Y = MathHelper.Clamp(box.Y - movementAmount, 0.1f, 0.9f); - break; - - case Key.J: - case Key.Down: - box.Y = MathHelper.Clamp(box.Y + movementAmount, 0.1f, 0.9f); - break; - - case Key.Z: - case Key.Left: - box.X = MathHelper.Clamp(box.X - movementAmount, 0.1f, 0.9f); - break; - - case Key.X: - case Key.Right: - box.X = MathHelper.Clamp(box.X + movementAmount, 0.1f, 0.9f); - break; - } - } - } - - lastFrameTime = Clock.CurrentTime; - } - } - - public class LatencyCursorContainer : CompositeDrawable - { - private Circle cursor = null!; - private InputManager inputManager = null!; - - private readonly BindableBool isActive; - - [Resolved] - private OverlayColourProvider overlayColourProvider { get; set; } = null!; - - public LatencyCursorContainer(BindableBool isActive) - { - this.isActive = isActive; - Masking = true; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - InternalChild = cursor = new Circle - { - Size = new Vector2(40), - Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour2, - }; - - inputManager = GetContainingInputManager(); - } - - protected override bool OnHover(HoverEvent e) => false; - - protected override void Update() - { - cursor.Colour = inputManager.CurrentState.Mouse.IsPressed(MouseButton.Left) ? overlayColourProvider.Content1 : overlayColourProvider.Colour2; - - if (isActive.Value) - { - cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position); - cursor.Alpha = 1; - } - else - { - cursor.Alpha = 0; - } - - base.Update(); - } - } } } diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 0a9d98450f..00821ab773 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -7,6 +7,7 @@ using System; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -62,6 +63,8 @@ namespace osu.Game.Screens.Utility [Resolved] private FrameworkConfigManager config { get; set; } = null!; + private readonly Bindable visualMode = new Bindable(); + private const int rounds_to_complete = 5; private const int rounds_to_complete_certified = 20; @@ -124,8 +127,9 @@ namespace osu.Game.Screens.Utility RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Text = @"Welcome to the latency certifier! -Use the arrow keys, Z/X/J/K to move the square. +Use the arrow keys, Z/X/F/J to control the display. Use the Tab key to change focus. +Change display modes with Space. Do whatever you need to try and perceive the difference in latency, then choose your best side. ", }, @@ -182,6 +186,10 @@ Do whatever you need to try and perceive the difference in latency, then choose { switch (e.Key) { + case Key.Space: + visualMode.Value = (LatencyVisualMode)(((int)visualMode.Value + 1) % 3); + return true; + case Key.Tab: var firstArea = mainArea.FirstOrDefault(a => !a.IsActiveArea.Value); if (firstArea != null) @@ -301,7 +309,9 @@ Do whatever you need to try and perceive the difference in latency, then choose isCertifying = true; changeDifficulty(DifficultyLevel - 1); }, - TooltipText = isPass ? $"Chain {rounds_to_complete_certified} rounds to confirm your perception!" : "You've reached your limits. Go to the previous level to complete certification!", + TooltipText = isPass + ? $"Chain {rounds_to_complete_certified} rounds to confirm your perception!" + : "You've reached your limits. Go to the previous level to complete certification!", }); } } @@ -386,12 +396,14 @@ Do whatever you need to try and perceive the difference in latency, then choose new LatencyArea(Key.Number1, betterSide == 1 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) { Width = 0.5f, + VisualMode = { BindTarget = visualMode }, IsActiveArea = { Value = true }, ReportUserBest = () => recordResult(betterSide == 0), }, new LatencyArea(Key.Number2, betterSide == 0 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) { Width = 0.5f, + VisualMode = { BindTarget = visualMode }, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, ReportUserBest = () => recordResult(betterSide == 1) diff --git a/osu.Game/Screens/Utility/LatencyVisualMode.cs b/osu.Game/Screens/Utility/LatencyVisualMode.cs new file mode 100644 index 0000000000..55dab86f9e --- /dev/null +++ b/osu.Game/Screens/Utility/LatencyVisualMode.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +namespace osu.Game.Screens.Utility +{ + public enum LatencyVisualMode + { + Simple, + CircleGameplay, + ScrollingGameplay, + } +} diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs new file mode 100644 index 0000000000..753310c3b3 --- /dev/null +++ b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Overlays; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Utility.SampleComponents +{ + public class LatencyCursorContainer : CompositeDrawable + { + private Circle cursor = null!; + private InputManager inputManager = null!; + + private readonly BindableBool isActive; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } = null!; + + public LatencyCursorContainer(BindableBool isActive) + { + this.isActive = isActive; + Masking = true; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChild = cursor = new Circle + { + Size = new Vector2(40), + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour2, + }; + + inputManager = GetContainingInputManager(); + } + + protected override bool OnHover(HoverEvent e) => false; + + protected override void Update() + { + cursor.Colour = inputManager.CurrentState.Mouse.IsPressed(MouseButton.Left) ? overlayColourProvider.Content1 : overlayColourProvider.Colour2; + + if (isActive.Value) + { + cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position); + cursor.Alpha = 1; + } + else + { + cursor.Alpha = 0; + } + + base.Update(); + } + } +} diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs b/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs new file mode 100644 index 0000000000..418d45489f --- /dev/null +++ b/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Overlays; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Utility.SampleComponents +{ + public class LatencyMovableBox : CompositeDrawable + { + private Box box = null!; + private InputManager inputManager = null!; + + private readonly BindableBool isActive; + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } = null!; + + public LatencyMovableBox(BindableBool isActive) + { + this.isActive = isActive; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + + InternalChild = box = new Box + { + Size = new Vector2(40), + RelativePositionAxes = Axes.Both, + Position = new Vector2(0.5f), + Origin = Anchor.Centre, + Colour = overlayColourProvider.Colour1, + }; + } + + protected override bool OnHover(HoverEvent e) => false; + + private double? lastFrameTime; + + protected override void Update() + { + base.Update(); + + if (!isActive.Value) + { + lastFrameTime = null; + box.Colour = overlayColourProvider.Colour1; + return; + } + + if (lastFrameTime != null) + { + float movementAmount = (float)(Clock.CurrentTime - lastFrameTime) / 400; + + var buttons = inputManager.CurrentState.Keyboard.Keys; + + box.Colour = buttons.HasAnyButtonPressed ? overlayColourProvider.Content1 : overlayColourProvider.Colour1; + + foreach (var key in buttons) + { + switch (key) + { + case Key.F: + case Key.Up: + box.Y = MathHelper.Clamp(box.Y - movementAmount, 0.1f, 0.9f); + break; + + case Key.K: + case Key.Down: + box.Y = MathHelper.Clamp(box.Y + movementAmount, 0.1f, 0.9f); + break; + + case Key.Z: + case Key.Left: + box.X = MathHelper.Clamp(box.X - movementAmount, 0.1f, 0.9f); + break; + + case Key.X: + case Key.Right: + box.X = MathHelper.Clamp(box.X + movementAmount, 0.1f, 0.9f); + break; + } + } + } + + lastFrameTime = Clock.CurrentTime; + } + } +} diff --git a/osu.Game/Screens/Utility/ScrollingGameplay.cs b/osu.Game/Screens/Utility/ScrollingGameplay.cs new file mode 100644 index 0000000000..31920d27e7 --- /dev/null +++ b/osu.Game/Screens/Utility/ScrollingGameplay.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Utility +{ + public class ScrollingGameplay : CompositeDrawable + { + } +} From e9547542ead87b0f2df143f191daf0d4d74009e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 17:17:09 +0900 Subject: [PATCH 02/27] Add circle gameplay --- osu.Game/Screens/Utility/CircleGameplay.cs | 163 ++++++++++++++++++++- 1 file changed, 162 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 0667a54ced..abd7c40532 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -2,11 +2,172 @@ // See the LICENCE file in the repository root for full licence text. #nullable enable +using System; +using System.Collections.Generic; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Utility { - public class CircleGameplay : CompositeDrawable + public class CircleGameplay : BeatSyncedContainer { + private int nextLocation; + + private OsuSpriteText unstableRate = null!; + + private readonly List hitEvents = new List(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + InternalChildren = new Drawable[] + { + unstableRate = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(size: 24) + } + }; + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + nextLocation++; + + Vector2 location; + + switch (nextLocation % 4) + { + default: + location = new Vector2(0.25f, 0.25f); + break; + + case 1: + location = new Vector2(0.75f, 0.75f); + break; + + case 2: + location = new Vector2(0.75f, 0.25f); + break; + + case 3: + location = new Vector2(0.25f, 0.75f); + break; + } + + AddInternal(new SampleHitCircle(Clock.CurrentTime + timingPoint.BeatLength) + { + RelativePositionAxes = Axes.Both, + Position = location, + Hit = hit, + }); + } + + private void hit(HitEvent h) + { + hitEvents.Add(h); + unstableRate.Text = $"{hitEvents.CalculateUnstableRate():N1}"; + } + + public class SampleHitCircle : CompositeDrawable + { + public HitEvent? HitEvent; + + public Action? Hit { get; set; } + + public readonly double HitTime; + + private readonly CircularContainer approach; + private readonly Circle circle; + + private const float size = 100; + + public SampleHitCircle(double hitTime) + { + HitTime = hitTime; + + Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + circle = new Circle + { + Colour = Color4.White, + Size = new Vector2(size), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + approach = new CircularContainer + { + BorderColour = Color4.Yellow, + Size = new Vector2(size), + Masking = true, + BorderThickness = 4, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + Alpha = 0, + AlwaysPresent = true, + RelativeSizeAxes = Axes.Both, + }, + } + }, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (HitEvent != null) + return false; + + approach.Expire(); + + circle + .FadeOut(200) + .ScaleTo(1.5f, 200); + + HitEvent = new HitEvent(Clock.CurrentTime - HitTime, HitResult.Good, new HitObject + { + HitWindows = new HitWindows(), + }, null, null); + + Hit?.Invoke(HitEvent.Value); + + this.Delay(200).Expire(); + + return true; + } + + protected override void Update() + { + base.Update(); + + approach.Scale = new Vector2((float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / 40, 1, 100)); + + if (Clock.CurrentTime > HitTime + 80) + Expire(); + } + } } } From c657ef272287d0c83d57f652363b23abb55eb2cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 17:27:06 +0900 Subject: [PATCH 03/27] Add ability to adjust spacing --- osu.Game/Screens/Utility/CircleGameplay.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index abd7c40532..c97b5dd7a7 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -51,22 +51,27 @@ namespace osu.Game.Screens.Utility Vector2 location; + const float spacing = 0.1f; + + const float spacing_low = 0.5f - spacing; + const float spacing_high = 0.5f + spacing; + switch (nextLocation % 4) { default: - location = new Vector2(0.25f, 0.25f); + location = new Vector2(spacing_low, spacing_low); break; case 1: - location = new Vector2(0.75f, 0.75f); + location = new Vector2(spacing_high, spacing_high); break; case 2: - location = new Vector2(0.75f, 0.25f); + location = new Vector2(spacing_high, spacing_low); break; case 3: - location = new Vector2(0.25f, 0.75f); + location = new Vector2(spacing_low, spacing_high); break; } @@ -97,6 +102,9 @@ namespace osu.Game.Screens.Utility private const float size = 100; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + => circle.ReceivePositionalInputAt(screenSpacePos); + public SampleHitCircle(double hitTime) { HitTime = hitTime; @@ -163,7 +171,7 @@ namespace osu.Game.Screens.Utility { base.Update(); - approach.Scale = new Vector2((float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / 40, 1, 100)); + approach.Scale = new Vector2((float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / 60, 1, 100)); if (Clock.CurrentTime > HitTime + 80) Expire(); From d46739ff0bc53f5dc2bceaa3894c3d9509ab99e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 18:18:18 +0900 Subject: [PATCH 04/27] Add circle gameplay test coverage --- .../Visual/Settings/TestSceneLatencyCertifierScreen.cs | 6 ++++++ osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs index af6681e9cf..079796df2e 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs @@ -24,6 +24,12 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("wait for load", () => latencyCertifier.IsLoaded); } + [Test] + public void TestCircleGameplay() + { + AddStep("set visual mode to circles", () => latencyCertifier.VisualMode.Value = LatencyVisualMode.CircleGameplay); + } + [Test] public void TestCertification() { diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 00821ab773..658e0537c6 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Utility [Resolved] private FrameworkConfigManager config { get; set; } = null!; - private readonly Bindable visualMode = new Bindable(); + public readonly Bindable VisualMode = new Bindable(); private const int rounds_to_complete = 5; @@ -187,7 +187,7 @@ Do whatever you need to try and perceive the difference in latency, then choose switch (e.Key) { case Key.Space: - visualMode.Value = (LatencyVisualMode)(((int)visualMode.Value + 1) % 3); + VisualMode.Value = (LatencyVisualMode)(((int)VisualMode.Value + 1) % 3); return true; case Key.Tab: @@ -396,14 +396,14 @@ Do whatever you need to try and perceive the difference in latency, then choose new LatencyArea(Key.Number1, betterSide == 1 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) { Width = 0.5f, - VisualMode = { BindTarget = visualMode }, + VisualMode = { BindTarget = VisualMode }, IsActiveArea = { Value = true }, ReportUserBest = () => recordResult(betterSide == 0), }, new LatencyArea(Key.Number2, betterSide == 0 ? mapDifficultyToTargetFrameRate(DifficultyLevel) : (int?)null) { Width = 0.5f, - VisualMode = { BindTarget = visualMode }, + VisualMode = { BindTarget = VisualMode }, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, ReportUserBest = () => recordResult(betterSide == 1) From 5c7d29cd31de5b62ba70cd37ff1904b2747fcd73 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 18:18:24 +0900 Subject: [PATCH 05/27] Remove dependency on game-wide audio --- osu.Game/Screens/Utility/CircleGameplay.cs | 38 +++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index c97b5dd7a7..2847f1b84d 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -4,14 +4,11 @@ #nullable enable using System; using System.Collections.Generic; -using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -20,7 +17,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.Utility { - public class CircleGameplay : BeatSyncedContainer + public class CircleGameplay : CompositeDrawable { private int nextLocation; @@ -28,6 +25,12 @@ namespace osu.Game.Screens.Utility private readonly List hitEvents = new List(); + private int? lastGeneratedBeat; + + private const double beat_length = 500; + private const double approach_rate_milliseconds = 100; + private const float spacing = 0.1f; + protected override void LoadComplete() { base.LoadComplete(); @@ -43,16 +46,26 @@ namespace osu.Game.Screens.Utility }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + protected override void Update() { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + base.Update(); + int nextBeat = (int)(Clock.CurrentTime / beat_length); + + if (lastGeneratedBeat == null || nextBeat != lastGeneratedBeat) + { + // generate four beats ahead to allow time for beats to display. + newBeat(nextBeat + 4); + lastGeneratedBeat = nextBeat; + } + } + + private void newBeat(int index) + { nextLocation++; Vector2 location; - const float spacing = 0.1f; - const float spacing_low = 0.5f - spacing; const float spacing_high = 0.5f + spacing; @@ -75,7 +88,7 @@ namespace osu.Game.Screens.Utility break; } - AddInternal(new SampleHitCircle(Clock.CurrentTime + timingPoint.BeatLength) + AddInternal(new SampleHitCircle(index * beat_length) { RelativePositionAxes = Axes.Both, Position = location, @@ -113,6 +126,8 @@ namespace osu.Game.Screens.Utility AutoSizeAxes = Axes.Both; + AlwaysPresent = true; + InternalChildren = new Drawable[] { circle = new Circle @@ -171,9 +186,10 @@ namespace osu.Game.Screens.Utility { base.Update(); - approach.Scale = new Vector2((float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / 60, 1, 100)); + approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds, 0, 100)); + Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); - if (Clock.CurrentTime > HitTime + 80) + if (Clock.CurrentTime > HitTime + 200) Expire(); } } From 5deaa42e9feaa8b9dfe5610e32225015d2f95df0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 18:20:15 +0900 Subject: [PATCH 06/27] Stop music on entering latency certification screen --- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 658e0537c6..2570db0e24 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -87,6 +87,9 @@ namespace osu.Game.Screens.Utility [Resolved] private GameHost host { get; set; } = null!; + [Resolved] + private MusicController musicController { get; set; } = null!; + public LatencyCertifierScreen() { InternalChildren = new Drawable[] @@ -166,6 +169,8 @@ Do whatever you need to try and perceive the difference in latency, then choose config.SetValue(FrameworkSetting.FrameSync, FrameSync.Unlimited); host.UpdateThread.ActiveHz = target_host_update_frames; host.AllowBenchmarkUnlimitedFrames = true; + + musicController.Stop(); } public override bool OnExiting(ScreenExitEvent e) From ba14d646c337a0e31e021a6077a2417e5f3d7729 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 18:27:58 +0900 Subject: [PATCH 07/27] Add static configuration --- osu.Game/Screens/Utility/CircleGameplay.cs | 79 ++++++++++++++++------ 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 2847f1b84d..91bf582fd6 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -4,12 +4,15 @@ #nullable enable using System; using System.Collections.Generic; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osuTK; @@ -25,11 +28,11 @@ namespace osu.Game.Screens.Utility private readonly List hitEvents = new List(); - private int? lastGeneratedBeat; + private double? lastGeneratedBeatTime; - private const double beat_length = 500; - private const double approach_rate_milliseconds = 100; - private const float spacing = 0.1f; + private static readonly BindableDouble beat_length = new BindableDouble(500) { MinValue = 200, MaxValue = 1000 }; + private static readonly BindableDouble approach_rate_milliseconds = new BindableDouble(100) { MinValue = 50, MaxValue = 500 }; + private static readonly BindableFloat spacing = new BindableFloat(0.2f) { MinValue = 0.05f, MaxValue = 0.4f }; protected override void LoadComplete() { @@ -37,12 +40,40 @@ namespace osu.Game.Screens.Utility InternalChildren = new Drawable[] { + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 400, + Spacing = new Vector2(2), + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new SettingsSlider + { + LabelText = "time spacing", + Current = beat_length + }, + new SettingsSlider + { + LabelText = "visual spacing", + Current = spacing + }, + new SettingsSlider + { + LabelText = "approach time", + Current = approach_rate_milliseconds + }, + } + }, unstableRate = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.Default.With(size: 24) - } + Font = OsuFont.Default.With(size: 24), + Y = -100, + }, }; } @@ -50,45 +81,53 @@ namespace osu.Game.Screens.Utility { base.Update(); - int nextBeat = (int)(Clock.CurrentTime / beat_length); + // We want to generate a few hit objects ahead of the current time (to allow them to animate). - if (lastGeneratedBeat == null || nextBeat != lastGeneratedBeat) + int nextBeat = (int)(Clock.CurrentTime / beat_length.Value) + 1; + + double generateUpTo = (nextBeat + 2) * beat_length.Value; + + while (lastGeneratedBeatTime == null || lastGeneratedBeatTime < generateUpTo) { - // generate four beats ahead to allow time for beats to display. - newBeat(nextBeat + 4); - lastGeneratedBeat = nextBeat; + double time = ++nextBeat * beat_length.Value; + + if (time <= lastGeneratedBeatTime) + continue; + + newBeat(time); + lastGeneratedBeatTime = time; } } - private void newBeat(int index) + private void newBeat(double time) { nextLocation++; Vector2 location; - const float spacing_low = 0.5f - spacing; - const float spacing_high = 0.5f + spacing; + float spacingLow = 0.5f - spacing.Value; + float spacingHigh = 0.5f + spacing.Value; switch (nextLocation % 4) { default: - location = new Vector2(spacing_low, spacing_low); + location = new Vector2(spacingLow, spacingLow); break; case 1: - location = new Vector2(spacing_high, spacing_high); + location = new Vector2(spacingHigh, spacingHigh); break; case 2: - location = new Vector2(spacing_high, spacing_low); + location = new Vector2(spacingHigh, spacingLow); break; case 3: - location = new Vector2(spacing_low, spacing_high); + location = new Vector2(spacingLow, spacingHigh); break; } - AddInternal(new SampleHitCircle(index * beat_length) + AddInternal(new SampleHitCircle(time) { RelativePositionAxes = Axes.Both, Position = location, @@ -186,7 +225,7 @@ namespace osu.Game.Screens.Utility { base.Update(); - approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds, 0, 100)); + approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds.Value, 0, 100)); Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); if (Clock.CurrentTime > HitTime + 200) From 7f4a54096f41fef7073153df5bbc8ce8e7bc30af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 18:37:53 +0900 Subject: [PATCH 08/27] Fix circles in the future being hittable --- osu.Game/Screens/Utility/CircleGameplay.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 91bf582fd6..c55c10dcb8 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -203,6 +203,9 @@ namespace osu.Game.Screens.Utility if (HitEvent != null) return false; + if (Math.Abs(Clock.CurrentTime - HitTime) > 200) + return false; + approach.Expire(); circle @@ -225,11 +228,14 @@ namespace osu.Game.Screens.Utility { base.Update(); - approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds.Value, 0, 100)); - Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); + if (HitEvent == null) + { + approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds.Value, 0, 100)); + Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); - if (Clock.CurrentTime > HitTime + 200) - Expire(); + if (Clock.CurrentTime > HitTime + 200) + Expire(); + } } } } From 2f1c331f65027aed7c3dafaf89d8717521728af9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 19:33:01 +0900 Subject: [PATCH 09/27] Move shared logic to `LatencySampleComponent` and implement key support for circle gameplay --- osu.Game/Screens/Utility/CircleGameplay.cs | 56 ++++++++++++------- osu.Game/Screens/Utility/LatencyArea.cs | 9 +-- .../LatencyCursorContainer.cs | 24 +++----- .../SampleComponents/LatencyMovableBox.cs | 36 +++--------- .../LatencySampleComponent.cs | 43 ++++++++++++++ 5 files changed, 101 insertions(+), 67 deletions(-) create mode 100644 osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index c55c10dcb8..bc3342fa3f 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -8,13 +8,14 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Input.States; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Utility.SampleComponents; using osuTK; using osuTK.Graphics; @@ -141,7 +142,7 @@ namespace osu.Game.Screens.Utility unstableRate.Text = $"{hitEvents.CalculateUnstableRate():N1}"; } - public class SampleHitCircle : CompositeDrawable + public class SampleHitCircle : LatencySampleComponent { public HitEvent? HitEvent; @@ -198,6 +199,8 @@ namespace osu.Game.Screens.Utility }; } + protected override bool OnHover(HoverEvent e) => true; + protected override bool OnMouseDown(MouseDownEvent e) { if (HitEvent != null) @@ -206,6 +209,37 @@ namespace osu.Game.Screens.Utility if (Math.Abs(Clock.CurrentTime - HitTime) > 200) return false; + attemptHit(); + return true; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!IsActive.Value) + return false; + + if (IsHovered) + attemptHit(); + return base.OnKeyDown(e); + } + + protected override void UpdateAtLimitedRate(InputState inputState) + { + if (HitEvent == null) + { + approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds.Value, 0, 100)); + Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); + + if (Clock.CurrentTime > HitTime + 200) + Expire(); + } + } + + private void attemptHit() => Schedule(() => + { + if (HitEvent != null) + return; + approach.Expire(); circle @@ -220,23 +254,7 @@ namespace osu.Game.Screens.Utility Hit?.Invoke(HitEvent.Value); this.Delay(200).Expire(); - - return true; - } - - protected override void Update() - { - base.Update(); - - if (HitEvent == null) - { - approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds.Value, 0, 100)); - Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); - - if (Clock.CurrentTime > HitTime + 200) - Expire(); - } - } + }); } } } diff --git a/osu.Game/Screens/Utility/LatencyArea.cs b/osu.Game/Screens/Utility/LatencyArea.cs index 5b48a10a49..21688f0b0c 100644 --- a/osu.Game/Screens/Utility/LatencyArea.cs +++ b/osu.Game/Screens/Utility/LatencyArea.cs @@ -16,6 +16,7 @@ using osuTK.Input; namespace osu.Game.Screens.Utility { + [Cached] public class LatencyArea : CompositeDrawable { [Resolved] @@ -82,11 +83,11 @@ namespace osu.Game.Screens.Utility case LatencyVisualMode.Simple: visualContent.Children = new Drawable[] { - new LatencyMovableBox(IsActiveArea) + new LatencyMovableBox { RelativeSizeAxes = Axes.Both, }, - new LatencyCursorContainer(IsActiveArea) + new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - new LatencyCursorContainer(IsActiveArea) + new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -114,7 +115,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - new LatencyCursorContainer(IsActiveArea) + new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs index 753310c3b3..e4c2b504cb 100644 --- a/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs +++ b/osu.Game/Screens/Utility/SampleComponents/LatencyCursorContainer.cs @@ -3,31 +3,25 @@ #nullable enable using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Input.States; using osu.Game.Overlays; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Utility.SampleComponents { - public class LatencyCursorContainer : CompositeDrawable + public class LatencyCursorContainer : LatencySampleComponent { private Circle cursor = null!; - private InputManager inputManager = null!; - - private readonly BindableBool isActive; [Resolved] private OverlayColourProvider overlayColourProvider { get; set; } = null!; - public LatencyCursorContainer(BindableBool isActive) + public LatencyCursorContainer() { - this.isActive = isActive; Masking = true; } @@ -41,27 +35,23 @@ namespace osu.Game.Screens.Utility.SampleComponents Origin = Anchor.Centre, Colour = overlayColourProvider.Colour2, }; - - inputManager = GetContainingInputManager(); } protected override bool OnHover(HoverEvent e) => false; - protected override void Update() + protected override void UpdateAtLimitedRate(InputState inputState) { - cursor.Colour = inputManager.CurrentState.Mouse.IsPressed(MouseButton.Left) ? overlayColourProvider.Content1 : overlayColourProvider.Colour2; + cursor.Colour = inputState.Mouse.IsPressed(MouseButton.Left) ? overlayColourProvider.Content1 : overlayColourProvider.Colour2; - if (isActive.Value) + if (IsActive.Value) { - cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position); + cursor.Position = ToLocalSpace(inputState.Mouse.Position); cursor.Alpha = 1; } else { cursor.Alpha = 0; } - - base.Update(); } } } diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs b/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs index 418d45489f..56c8aa2ed5 100644 --- a/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs +++ b/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs @@ -2,47 +2,31 @@ // See the LICENCE file in the repository root for full licence text. #nullable enable -using osu.Framework.Allocation; -using osu.Framework.Bindables; + using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Game.Overlays; +using osu.Framework.Input.States; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Utility.SampleComponents { - public class LatencyMovableBox : CompositeDrawable + public class LatencyMovableBox : LatencySampleComponent { private Box box = null!; - private InputManager inputManager = null!; - - private readonly BindableBool isActive; - - [Resolved] - private OverlayColourProvider overlayColourProvider { get; set; } = null!; - - public LatencyMovableBox(BindableBool isActive) - { - this.isActive = isActive; - } protected override void LoadComplete() { base.LoadComplete(); - inputManager = GetContainingInputManager(); - InternalChild = box = new Box { Size = new Vector2(40), RelativePositionAxes = Axes.Both, Position = new Vector2(0.5f), Origin = Anchor.Centre, - Colour = overlayColourProvider.Colour1, + Colour = OverlayColourProvider.Colour1, }; } @@ -50,14 +34,12 @@ namespace osu.Game.Screens.Utility.SampleComponents private double? lastFrameTime; - protected override void Update() + protected override void UpdateAtLimitedRate(InputState inputState) { - base.Update(); - - if (!isActive.Value) + if (!IsActive.Value) { lastFrameTime = null; - box.Colour = overlayColourProvider.Colour1; + box.Colour = OverlayColourProvider.Colour1; return; } @@ -65,9 +47,9 @@ namespace osu.Game.Screens.Utility.SampleComponents { float movementAmount = (float)(Clock.CurrentTime - lastFrameTime) / 400; - var buttons = inputManager.CurrentState.Keyboard.Keys; + var buttons = inputState.Keyboard.Keys; - box.Colour = buttons.HasAnyButtonPressed ? overlayColourProvider.Content1 : overlayColourProvider.Colour1; + box.Colour = buttons.HasAnyButtonPressed ? OverlayColourProvider.Content1 : OverlayColourProvider.Colour1; foreach (var key in buttons) { diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs b/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs new file mode 100644 index 0000000000..03f6b46852 --- /dev/null +++ b/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Input.States; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Utility.SampleComponents +{ + public abstract class LatencySampleComponent : CompositeDrawable + { + protected readonly BindableBool IsActive = new BindableBool(); + + private InputManager inputManager = null!; + + [Resolved] + private LatencyArea latencyArea { get; set; } = null!; + + [Resolved] + protected OverlayColourProvider OverlayColourProvider { get; private set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + IsActive.BindTo(latencyArea.IsActiveArea); + } + + protected sealed override void Update() + { + base.Update(); + UpdateAtLimitedRate(inputManager.CurrentState); + } + + protected abstract void UpdateAtLimitedRate(InputState inputState); + } +} From 41d16c613da5a93cc48b97f8b62ab00ff480bd11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 19:35:21 +0900 Subject: [PATCH 10/27] Fix being able to hit way too early using keyboard --- osu.Game/Screens/Utility/CircleGameplay.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index bc3342fa3f..d31b98fe6e 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -218,6 +218,9 @@ namespace osu.Game.Screens.Utility if (!IsActive.Value) return false; + if (Math.Abs(Clock.CurrentTime - HitTime) > 200) + return false; + if (IsHovered) attemptHit(); return base.OnKeyDown(e); @@ -240,6 +243,10 @@ namespace osu.Game.Screens.Utility if (HitEvent != null) return; + // in case it was hit outside of display range, show immediately + // so the user isn't confused. + this.FadeIn(); + approach.Expire(); circle From a6fd61c4448a4e77495f41a95f04a8abcaeb6999 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 19:42:07 +0900 Subject: [PATCH 11/27] Default to circle gameplay mode --- osu.Game/Screens/Utility/LatencyVisualMode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Utility/LatencyVisualMode.cs b/osu.Game/Screens/Utility/LatencyVisualMode.cs index 55dab86f9e..cc15f79be8 100644 --- a/osu.Game/Screens/Utility/LatencyVisualMode.cs +++ b/osu.Game/Screens/Utility/LatencyVisualMode.cs @@ -6,8 +6,8 @@ namespace osu.Game.Screens.Utility { public enum LatencyVisualMode { - Simple, CircleGameplay, ScrollingGameplay, + Simple, } } From ef5c1a1ecbd51d82b92fc6490c540a4c002d3fd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 19:42:17 +0900 Subject: [PATCH 12/27] Improve visuals of circle gameplay mode --- osu.Game/Screens/Utility/CircleGameplay.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index d31b98fe6e..9fb8eb2761 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -4,6 +4,7 @@ #nullable enable using System; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,7 +18,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Utility.SampleComponents; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Utility { @@ -150,8 +150,8 @@ namespace osu.Game.Screens.Utility public readonly double HitTime; - private readonly CircularContainer approach; - private readonly Circle circle; + private CircularContainer approach = null!; + private Circle circle = null!; private const float size = 100; @@ -163,23 +163,25 @@ namespace osu.Game.Screens.Utility HitTime = hitTime; Origin = Anchor.Centre; - AutoSizeAxes = Axes.Both; - AlwaysPresent = true; + } + [BackgroundDependencyLoader] + private void load() + { InternalChildren = new Drawable[] { circle = new Circle { - Colour = Color4.White, + Colour = OverlayColourProvider.Content1, Size = new Vector2(size), Anchor = Anchor.Centre, Origin = Anchor.Centre, }, approach = new CircularContainer { - BorderColour = Color4.Yellow, + BorderColour = OverlayColourProvider.Colour1, Size = new Vector2(size), Masking = true, BorderThickness = 4, @@ -189,7 +191,6 @@ namespace osu.Game.Screens.Utility { new Box { - Colour = Color4.Black, Alpha = 0, AlwaysPresent = true, RelativeSizeAxes = Axes.Both, From 837958b254943d025914db3c664ba1784137aeba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 19:43:09 +0900 Subject: [PATCH 13/27] Remove scrolling gameplay mode for now --- osu.Game/Screens/Utility/LatencyArea.cs | 14 -------------- osu.Game/Screens/Utility/LatencyVisualMode.cs | 1 - 2 files changed, 15 deletions(-) diff --git a/osu.Game/Screens/Utility/LatencyArea.cs b/osu.Game/Screens/Utility/LatencyArea.cs index 21688f0b0c..3b9e95ce16 100644 --- a/osu.Game/Screens/Utility/LatencyArea.cs +++ b/osu.Game/Screens/Utility/LatencyArea.cs @@ -108,20 +108,6 @@ 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(); } diff --git a/osu.Game/Screens/Utility/LatencyVisualMode.cs b/osu.Game/Screens/Utility/LatencyVisualMode.cs index cc15f79be8..f9f51f0829 100644 --- a/osu.Game/Screens/Utility/LatencyVisualMode.cs +++ b/osu.Game/Screens/Utility/LatencyVisualMode.cs @@ -7,7 +7,6 @@ namespace osu.Game.Screens.Utility public enum LatencyVisualMode { CircleGameplay, - ScrollingGameplay, Simple, } } From 18f74b2840fd2e3ecc8bb8322262e5fa5c3a6a98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 20:04:51 +0900 Subject: [PATCH 14/27] Move settings and bindables to a sane location --- osu.Game/Screens/Utility/CircleGameplay.cs | 68 +++++++------------ .../Screens/Utility/LatencyCertifierScreen.cs | 60 ++++++++++++++-- .../LatencySampleComponent.cs | 12 ++++ 3 files changed, 90 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 9fb8eb2761..4bfc10548f 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -13,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Utility.SampleComponents; @@ -21,7 +19,7 @@ using osuTK; namespace osu.Game.Screens.Utility { - public class CircleGameplay : CompositeDrawable + public class CircleGameplay : LatencySampleComponent { private int nextLocation; @@ -31,9 +29,7 @@ namespace osu.Game.Screens.Utility private double? lastGeneratedBeatTime; - private static readonly BindableDouble beat_length = new BindableDouble(500) { MinValue = 200, MaxValue = 1000 }; - private static readonly BindableDouble approach_rate_milliseconds = new BindableDouble(100) { MinValue = 50, MaxValue = 500 }; - private static readonly BindableFloat spacing = new BindableFloat(0.2f) { MinValue = 0.05f, MaxValue = 0.4f }; + private Container circles = null!; protected override void LoadComplete() { @@ -41,33 +37,6 @@ namespace osu.Game.Screens.Utility InternalChildren = new Drawable[] { - new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - Width = 400, - Spacing = new Vector2(2), - Direction = FillDirection.Vertical, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new SettingsSlider - { - LabelText = "time spacing", - Current = beat_length - }, - new SettingsSlider - { - LabelText = "visual spacing", - Current = spacing - }, - new SettingsSlider - { - LabelText = "approach time", - Current = approach_rate_milliseconds - }, - } - }, unstableRate = new OsuSpriteText { Anchor = Anchor.Centre, @@ -75,22 +44,31 @@ namespace osu.Game.Screens.Utility Font = OsuFont.Default.With(size: 24), Y = -100, }, + circles = new Container + { + RelativeSizeAxes = Axes.Both, + }, }; + + SampleBPM.BindValueChanged(_ => + { + circles.Clear(); + lastGeneratedBeatTime = null; + }); } - protected override void Update() + protected override void UpdateAtLimitedRate(InputState inputState) { - base.Update(); + 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). - - int nextBeat = (int)(Clock.CurrentTime / beat_length.Value) + 1; - - double generateUpTo = (nextBeat + 2) * beat_length.Value; + double generateUpTo = (nextBeat + 2) * beatLength; while (lastGeneratedBeatTime == null || lastGeneratedBeatTime < generateUpTo) { - double time = ++nextBeat * beat_length.Value; + double time = ++nextBeat * beatLength; if (time <= lastGeneratedBeatTime) continue; @@ -106,8 +84,8 @@ namespace osu.Game.Screens.Utility Vector2 location; - float spacingLow = 0.5f - spacing.Value; - float spacingHigh = 0.5f + spacing.Value; + float spacingLow = 0.5f - SampleVisualSpacing.Value; + float spacingHigh = 0.5f + SampleVisualSpacing.Value; switch (nextLocation % 4) { @@ -128,7 +106,7 @@ namespace osu.Game.Screens.Utility break; } - AddInternal(new SampleHitCircle(time) + circles.Add(new SampleHitCircle(time) { RelativePositionAxes = Axes.Both, Position = location, @@ -139,7 +117,7 @@ namespace osu.Game.Screens.Utility private void hit(HitEvent h) { hitEvents.Add(h); - unstableRate.Text = $"{hitEvents.CalculateUnstableRate():N1}"; + unstableRate.Text = $"UR: {hitEvents.CalculateUnstableRate()}"; } public class SampleHitCircle : LatencySampleComponent @@ -231,7 +209,7 @@ namespace osu.Game.Screens.Utility { if (HitEvent == null) { - approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / approach_rate_milliseconds.Value, 0, 100)); + approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / SampleApproachRate.Value, 0, 100)); Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); if (Clock.CurrentTime > HitTime + 200) diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 2570db0e24..79736ea17d 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -25,11 +25,13 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Utility { + [Cached] public class LatencyCertifierScreen : OsuScreen { private FrameSync previousFrameSyncMode; @@ -49,6 +51,10 @@ namespace osu.Game.Screens.Utility private readonly Container resultsArea; + public readonly BindableDouble SampleBPM = new BindableDouble(120) { MinValue = 60, MaxValue = 300 }; + public readonly BindableDouble SampleApproachRate = new BindableDouble(100) { MinValue = 50, MaxValue = 500 }; + public readonly BindableFloat SampleVisualSpacing = new BindableFloat(0.2f) { MinValue = 0.05f, MaxValue = 0.3f }; + /// /// The rate at which the game host should attempt to run. /// @@ -84,6 +90,8 @@ namespace osu.Game.Screens.Utility private double lastPoll; private int pollingMax; + private readonly FillFlowContainer settings; + [Resolved] private GameHost host { get; set; } = null!; @@ -122,19 +130,30 @@ namespace osu.Game.Screens.Utility Anchor = Anchor.TopCentre, Origin = Anchor.TopRight, }, - explanatoryText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + settings = new FillFlowContainer { + Name = "Settings", + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding(10), + Spacing = new Vector2(2), + Direction = FillDirection.Vertical, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - TextAnchor = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Text = @"Welcome to the latency certifier! + Child = explanatoryText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, + Text = @"Welcome to the latency certifier! Use the arrow keys, Z/X/F/J to control the display. Use the Tab key to change focus. Change display modes with Space. Do whatever you need to try and perceive the difference in latency, then choose your best side. ", + }, }, resultsArea = new Container { @@ -423,6 +442,37 @@ Do whatever you need to try and perceive the difference in latency, then choose mainArea.Children.First(a => a != area).IsActiveArea.Value = false; }); } + + settings.AddRange(new Drawable[] + { + new SettingsSlider + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.None, + Width = 400, + LabelText = "bpm", + Current = SampleBPM + }, + new SettingsSlider + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.None, + Width = 400, + LabelText = "visual spacing", + Current = SampleVisualSpacing + }, + new SettingsSlider + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.None, + Width = 400, + LabelText = "approach rate", + Current = SampleApproachRate + }, + }); } private void recordResult(bool correct) diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs b/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs index 03f6b46852..c3233d5aa5 100644 --- a/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs +++ b/osu.Game/Screens/Utility/SampleComponents/LatencySampleComponent.cs @@ -14,6 +14,10 @@ namespace osu.Game.Screens.Utility.SampleComponents { public abstract class LatencySampleComponent : CompositeDrawable { + protected readonly BindableDouble SampleBPM = new BindableDouble(); + protected readonly BindableDouble SampleApproachRate = new BindableDouble(); + protected readonly BindableFloat SampleVisualSpacing = new BindableFloat(); + protected readonly BindableBool IsActive = new BindableBool(); private InputManager inputManager = null!; @@ -24,6 +28,14 @@ namespace osu.Game.Screens.Utility.SampleComponents [Resolved] protected OverlayColourProvider OverlayColourProvider { get; private set; } = null!; + [BackgroundDependencyLoader] + private void load(LatencyCertifierScreen latencyCertifierScreen) + { + SampleBPM.BindTo(latencyCertifierScreen.SampleBPM); + SampleApproachRate.BindTo(latencyCertifierScreen.SampleApproachRate); + SampleVisualSpacing.BindTo(latencyCertifierScreen.SampleVisualSpacing); + } + protected override void LoadComplete() { base.LoadComplete(); From 213ccfb74330bc4f7b42068e6be881213cbf25ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 20:08:53 +0900 Subject: [PATCH 15/27] Improve explanation text and add link to wiki --- .../Screens/Utility/LatencyCertifierScreen.cs | 18 +++++++++--------- .../SampleComponents/LatencyMovableBox.cs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 79736ea17d..bcdba370f5 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Utility public override float BackgroundParallaxAmount => 0; - private readonly OsuTextFlowContainer explanatoryText; + private readonly LinkFlowContainer explanatoryText; private readonly Container mainArea; @@ -134,25 +134,19 @@ namespace osu.Game.Screens.Utility { Name = "Settings", AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, + Width = 800, Padding = new MarginPadding(10), Spacing = new Vector2(2), Direction = FillDirection.Vertical, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Child = explanatoryText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + Child = explanatoryText = new LinkFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, TextAnchor = Anchor.TopCentre, - Text = @"Welcome to the latency certifier! -Use the arrow keys, Z/X/F/J to control the display. -Use the Tab key to change focus. -Change display modes with Space. -Do whatever you need to try and perceive the difference in latency, then choose your best side. -", }, }, resultsArea = new Container @@ -169,6 +163,12 @@ Do whatever you need to try and perceive the difference in latency, then choose AutoSizeAxes = Axes.Y, }, }; + + explanatoryText.AddParagraph(@"Welcome to the latency certifier!"); + explanatoryText.AddParagraph(@"Do whatever you need to try and perceive the difference in latency, then choose your best side. Read more about the methodology "); + explanatoryText.AddLink("here", "https://github.com/ppy/osu/wiki/Latency-and-unlimited-frame-rates"); + explanatoryText.AddParagraph(@"Use the arrow keys or Z/X/F/J to control the display."); + explanatoryText.AddParagraph(@"Tab key to change focus. Space to change display mode"); } protected override bool OnMouseMove(MouseMoveEvent e) diff --git a/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs b/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs index 56c8aa2ed5..a7da05fbb1 100644 --- a/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs +++ b/osu.Game/Screens/Utility/SampleComponents/LatencyMovableBox.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Utility.SampleComponents box.Y = MathHelper.Clamp(box.Y - movementAmount, 0.1f, 0.9f); break; - case Key.K: + case Key.J: case Key.Down: box.Y = MathHelper.Clamp(box.Y + movementAmount, 0.1f, 0.9f); break; From b956a916c1309f3fd599938dfea6fea39c152217 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 20:21:03 +0900 Subject: [PATCH 16/27] Make units sane --- osu.Game/Screens/Utility/CircleGameplay.cs | 11 ++++++++--- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 4bfc10548f..5f08367336 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -10,6 +10,7 @@ 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.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects; @@ -84,8 +85,10 @@ namespace osu.Game.Screens.Utility Vector2 location; - float spacingLow = 0.5f - SampleVisualSpacing.Value; - float spacingHigh = 0.5f + SampleVisualSpacing.Value; + float adjust = SampleVisualSpacing.Value * 0.25f; + + float spacingLow = 0.5f - adjust; + float spacingHigh = 0.5f + adjust; switch (nextLocation % 4) { @@ -209,7 +212,9 @@ namespace osu.Game.Screens.Utility { if (HitEvent == null) { - approach.Scale = new Vector2(1 + (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / SampleApproachRate.Value, 0, 100)); + double preempt = (float)IBeatmapDifficultyInfo.DifficultyRange(SampleApproachRate.Value, 1800, 1200, 450); + + approach.Scale = new Vector2(1 + 4 * (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / preempt, 0, 100)); Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); if (Clock.CurrentTime > HitTime + 200) diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index bcdba370f5..251a94e705 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -52,8 +52,8 @@ namespace osu.Game.Screens.Utility private readonly Container resultsArea; public readonly BindableDouble SampleBPM = new BindableDouble(120) { MinValue = 60, MaxValue = 300 }; - public readonly BindableDouble SampleApproachRate = new BindableDouble(100) { MinValue = 50, MaxValue = 500 }; - public readonly BindableFloat SampleVisualSpacing = new BindableFloat(0.2f) { MinValue = 0.05f, MaxValue = 0.3f }; + public readonly BindableDouble SampleApproachRate = new BindableDouble(9) { MinValue = 5, MaxValue = 12, Precision = 0.1 }; + public readonly BindableFloat SampleVisualSpacing = new BindableFloat(0.5f) { MinValue = 0f, MaxValue = 1, Precision = 0.1f }; /// /// The rate at which the game host should attempt to run. From 8ce545ff9d87f13706ed77182988b9baeb2dec2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 20:25:33 +0900 Subject: [PATCH 17/27] Fix visuals in circle gameplay and greedy hover --- osu.Game/Screens/Utility/CircleGameplay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 5f08367336..47129c7581 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Utility private void hit(HitEvent h) { hitEvents.Add(h); - unstableRate.Text = $"UR: {hitEvents.CalculateUnstableRate()}"; + unstableRate.Text = $"UR: {hitEvents.CalculateUnstableRate():N0}"; } public class SampleHitCircle : LatencySampleComponent @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Utility } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { InternalChildren = new Drawable[] { @@ -162,7 +162,7 @@ namespace osu.Game.Screens.Utility }, approach = new CircularContainer { - BorderColour = OverlayColourProvider.Colour1, + BorderColour = colours.Blue, Size = new Vector2(size), Masking = true, BorderThickness = 4, @@ -181,8 +181,6 @@ namespace osu.Game.Screens.Utility }; } - protected override bool OnHover(HoverEvent e) => true; - protected override bool OnMouseDown(MouseDownEvent e) { if (HitEvent != null) From 096d6df8684d5eda673ef6f081c3c744ee4f5daa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jun 2022 20:33:01 +0900 Subject: [PATCH 18/27] Fix regression in testing and setting logic --- .../TestSceneLatencyCertifierScreen.cs | 1 - .../Screens/Utility/LatencyCertifierScreen.cs | 78 +++++++++---------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs index 079796df2e..ee18afcbaf 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs @@ -44,7 +44,6 @@ namespace osu.Game.Tests.Visual.Settings clickUntilResults(true); AddAssert("check at results", () => !latencyCertifier.ChildrenOfType().Any()); - AddAssert("check no buttons", () => !latencyCertifier.ChildrenOfType().Any()); checkDifficulty(1); } diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 251a94e705..00b6a25ac3 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -90,8 +90,6 @@ namespace osu.Game.Screens.Utility private double lastPoll; private int pollingMax; - private readonly FillFlowContainer settings; - [Resolved] private GameHost host { get; set; } = null!; @@ -130,7 +128,7 @@ namespace osu.Game.Screens.Utility Anchor = Anchor.TopCentre, Origin = Anchor.TopRight, }, - settings = new FillFlowContainer + new FillFlowContainer { Name = "Settings", AutoSizeAxes = Axes.Y, @@ -140,13 +138,43 @@ namespace osu.Game.Screens.Utility Direction = FillDirection.Vertical, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Child = explanatoryText = new LinkFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + Children = new Drawable[] { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextAnchor = Anchor.TopCentre, + explanatoryText = new LinkFlowContainer(cp => cp.Font = OsuFont.Default.With(size: 20)) + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, + }, + new SettingsSlider + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.None, + Width = 400, + LabelText = "bpm", + Current = SampleBPM + }, + new SettingsSlider + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.None, + Width = 400, + LabelText = "visual spacing", + Current = SampleVisualSpacing + }, + new SettingsSlider + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.None, + Width = 400, + LabelText = "approach rate", + Current = SampleApproachRate + }, }, }, resultsArea = new Container @@ -227,6 +255,7 @@ namespace osu.Game.Screens.Utility private void showResults() { mainArea.Clear(); + resultsArea.Clear(); var displayMode = host.Window?.CurrentDisplayMode.Value; @@ -442,37 +471,6 @@ namespace osu.Game.Screens.Utility mainArea.Children.First(a => a != area).IsActiveArea.Value = false; }); } - - settings.AddRange(new Drawable[] - { - new SettingsSlider - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.None, - Width = 400, - LabelText = "bpm", - Current = SampleBPM - }, - new SettingsSlider - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.None, - Width = 400, - LabelText = "visual spacing", - Current = SampleVisualSpacing - }, - new SettingsSlider - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.None, - Width = 400, - LabelText = "approach rate", - Current = SampleApproachRate - }, - }); } private void recordResult(bool correct) From 68da9f038661544f54c3a102e0a13527d3a3945d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Jun 2022 21:27:44 +0900 Subject: [PATCH 19/27] Add explicit precision for BPM adjustment Co-authored-by: Salman Ahmed --- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 00b6a25ac3..2195298ff1 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Utility private readonly Container resultsArea; - public readonly BindableDouble SampleBPM = new BindableDouble(120) { MinValue = 60, MaxValue = 300 }; + public readonly BindableDouble SampleBPM = new BindableDouble(120) { MinValue = 60, MaxValue = 300, Precision = 1 }; public readonly BindableDouble SampleApproachRate = new BindableDouble(9) { MinValue = 5, MaxValue = 12, Precision = 0.1 }; public readonly BindableFloat SampleVisualSpacing = new BindableFloat(0.5f) { MinValue = 0f, MaxValue = 1, Precision = 0.1f }; From d130e7ebff5167f48f4d39ef108aa33abc98dc80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Jun 2022 21:30:33 +0900 Subject: [PATCH 20/27] Remove `ScrollingGameplay` class for now --- osu.Game/Screens/Utility/ScrollingGameplay.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 osu.Game/Screens/Utility/ScrollingGameplay.cs diff --git a/osu.Game/Screens/Utility/ScrollingGameplay.cs b/osu.Game/Screens/Utility/ScrollingGameplay.cs deleted file mode 100644 index 31920d27e7..0000000000 --- a/osu.Game/Screens/Utility/ScrollingGameplay.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable enable -using osu.Framework.Graphics.Containers; - -namespace osu.Game.Screens.Utility -{ - public class ScrollingGameplay : CompositeDrawable - { - } -} From c697dc90e4ae1fca938104920a3092519aa220b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Jun 2022 21:32:02 +0900 Subject: [PATCH 21/27] Hide settings at results screen --- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 2195298ff1..558a16db6d 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -90,6 +90,8 @@ namespace osu.Game.Screens.Utility private double lastPoll; private int pollingMax; + private readonly FillFlowContainer settings; + [Resolved] private GameHost host { get; set; } = null!; @@ -128,7 +130,7 @@ namespace osu.Game.Screens.Utility Anchor = Anchor.TopCentre, Origin = Anchor.TopRight, }, - new FillFlowContainer + settings = new FillFlowContainer { Name = "Settings", AutoSizeAxes = Axes.Y, @@ -256,6 +258,7 @@ namespace osu.Game.Screens.Utility { mainArea.Clear(); resultsArea.Clear(); + settings.Hide(); var displayMode = host.Window?.CurrentDisplayMode.Value; @@ -437,6 +440,8 @@ namespace osu.Game.Screens.Utility private void loadNextRound() { + settings.Show(); + attemptsAtCurrentDifficulty++; statusText.Text = $"Level {DifficultyLevel}\nRound {attemptsAtCurrentDifficulty} of {totalRoundForNextResultsScreen}"; From 0c333e5c0884ece7e336e1656fafd56a2e5a9650 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Jun 2022 21:34:00 +0900 Subject: [PATCH 22/27] Link directly to methodology section Co-authored-by: Salman Ahmed --- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 558a16db6d..6bd07ac1a4 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -196,7 +196,7 @@ namespace osu.Game.Screens.Utility explanatoryText.AddParagraph(@"Welcome to the latency certifier!"); explanatoryText.AddParagraph(@"Do whatever you need to try and perceive the difference in latency, then choose your best side. Read more about the methodology "); - explanatoryText.AddLink("here", "https://github.com/ppy/osu/wiki/Latency-and-unlimited-frame-rates"); + explanatoryText.AddLink("here", "https://github.com/ppy/osu/wiki/Latency-and-unlimited-frame-rates#methodology"); explanatoryText.AddParagraph(@"Use the arrow keys or Z/X/F/J to control the display."); explanatoryText.AddParagraph(@"Tab key to change focus. Space to change display mode"); } From 8d53ed64a3d8da3d3925a8675c9eb590cf878141 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Jun 2022 21:36:41 +0900 Subject: [PATCH 23/27] Fix mode cycling and add test coverage --- .../Visual/Settings/TestSceneLatencyCertifierScreen.cs | 6 ++++++ osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs index ee18afcbaf..151d9236ea 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs @@ -30,6 +30,12 @@ namespace osu.Game.Tests.Visual.Settings AddStep("set visual mode to circles", () => latencyCertifier.VisualMode.Value = LatencyVisualMode.CircleGameplay); } + [Test] + public void TestCycleVisualModes() + { + AddRepeatStep("cycle mode", () => InputManager.Key(Key.Space), 6); + } + [Test] public void TestCertification() { diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 6bd07ac1a4..514fa42248 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -241,7 +241,8 @@ namespace osu.Game.Screens.Utility switch (e.Key) { case Key.Space: - VisualMode.Value = (LatencyVisualMode)(((int)VisualMode.Value + 1) % 3); + int availableModes = Enum.GetValues(typeof(LatencyVisualMode)).Length; + VisualMode.Value = (LatencyVisualMode)(((int)VisualMode.Value + 1) % availableModes); return true; case Key.Tab: From 419b5791c968e15889150d24beabd1e971ee9783 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Jun 2022 15:50:34 +0300 Subject: [PATCH 24/27] Move circle duration to constant --- osu.Game/Screens/Utility/CircleGameplay.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 47129c7581..6c7cc0b80b 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -135,6 +135,7 @@ namespace osu.Game.Screens.Utility private Circle circle = null!; private const float size = 100; + private const float duration = 200; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); @@ -186,7 +187,7 @@ namespace osu.Game.Screens.Utility if (HitEvent != null) return false; - if (Math.Abs(Clock.CurrentTime - HitTime) > 200) + if (Math.Abs(Clock.CurrentTime - HitTime) > duration) return false; attemptHit(); @@ -198,7 +199,7 @@ namespace osu.Game.Screens.Utility if (!IsActive.Value) return false; - if (Math.Abs(Clock.CurrentTime - HitTime) > 200) + if (Math.Abs(Clock.CurrentTime - HitTime) > duration) return false; if (IsHovered) @@ -215,7 +216,7 @@ namespace osu.Game.Screens.Utility approach.Scale = new Vector2(1 + 4 * (float)MathHelper.Clamp((HitTime - Clock.CurrentTime) / preempt, 0, 100)); Alpha = (float)MathHelper.Clamp((Clock.CurrentTime - HitTime + 600) / 400, 0, 1); - if (Clock.CurrentTime > HitTime + 200) + if (Clock.CurrentTime > HitTime + duration) Expire(); } } @@ -232,8 +233,8 @@ namespace osu.Game.Screens.Utility approach.Expire(); circle - .FadeOut(200) - .ScaleTo(1.5f, 200); + .FadeOut(duration) + .ScaleTo(1.5f, duration); HitEvent = new HitEvent(Clock.CurrentTime - HitTime, HitResult.Good, new HitObject { @@ -242,7 +243,7 @@ namespace osu.Game.Screens.Utility Hit?.Invoke(HitEvent.Value); - this.Delay(200).Expire(); + this.Delay(duration).Expire(); }); } } From 5b3b9a2cd34541a20abee4d53f8e12621fff2090 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Jun 2022 16:21:22 +0300 Subject: [PATCH 25/27] Add test coverage for "simple" mode --- .../Visual/Settings/TestSceneLatencyCertifierScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs index 151d9236ea..d6e0f995e5 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneLatencyCertifierScreen.cs @@ -24,6 +24,12 @@ namespace osu.Game.Tests.Visual.Settings AddUntilStep("wait for load", () => latencyCertifier.IsLoaded); } + [Test] + public void TestSimple() + { + AddStep("set visual mode to simple", () => latencyCertifier.VisualMode.Value = LatencyVisualMode.Simple); + } + [Test] public void TestCircleGameplay() { From af353c37c0b1a021d409aecfc267799abd0a2023 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 11 Jun 2022 23:54:11 +0900 Subject: [PATCH 26/27] Don't show UR for now --- osu.Game/Screens/Utility/CircleGameplay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index 6c7cc0b80b..d710b26738 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -120,7 +120,10 @@ namespace osu.Game.Screens.Utility private void hit(HitEvent h) { hitEvents.Add(h); - unstableRate.Text = $"UR: {hitEvents.CalculateUnstableRate():N0}"; + + // Disabled to keep things simple based on internal feedback nothing it's not helpful. + // Can be reconsidered in the future. + // unstableRate.Text = $"UR: {hitEvents.CalculateUnstableRate():N0}"; } public class SampleHitCircle : LatencySampleComponent From 486f762f44cf3d1c7173e01c23cab5135307e388 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 12 Jun 2022 00:13:35 +0900 Subject: [PATCH 27/27] Fix inspection by removing unstable rate code --- osu.Game/Screens/Utility/CircleGameplay.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/osu.Game/Screens/Utility/CircleGameplay.cs b/osu.Game/Screens/Utility/CircleGameplay.cs index d710b26738..dd59721a65 100644 --- a/osu.Game/Screens/Utility/CircleGameplay.cs +++ b/osu.Game/Screens/Utility/CircleGameplay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. #nullable enable + using System; using System.Collections.Generic; using osu.Framework.Allocation; @@ -12,7 +13,6 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Utility.SampleComponents; @@ -24,8 +24,6 @@ namespace osu.Game.Screens.Utility { private int nextLocation; - private OsuSpriteText unstableRate = null!; - private readonly List hitEvents = new List(); private double? lastGeneratedBeatTime; @@ -38,13 +36,6 @@ namespace osu.Game.Screens.Utility InternalChildren = new Drawable[] { - unstableRate = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Default.With(size: 24), - Y = -100, - }, circles = new Container { RelativeSizeAxes = Axes.Both, @@ -120,10 +111,6 @@ namespace osu.Game.Screens.Utility private void hit(HitEvent h) { hitEvents.Add(h); - - // Disabled to keep things simple based on internal feedback nothing it's not helpful. - // Can be reconsidered in the future. - // unstableRate.Text = $"UR: {hitEvents.CalculateUnstableRate():N0}"; } public class SampleHitCircle : LatencySampleComponent