mirror of
https://github.com/ppy/osu.git
synced 2024-12-16 17:42:54 +08:00
e5b51f769c
Checking the delta after the application of rate is not correct. The delta is in screen-space *before* the rate from rate-changing mods were applied; the point of the application of the rate is to compensate for the fact that the spinner is still judged in "track time" - but the goal is to keep the spinner's difficulty *independent* of rate, which means that with DT active the user's spin is "twice as effective" to compensate for the fact that the spinner is twice as short in real time. In another formulation, with DT active, the user gets to record replay frames "half as often" as in normal gameplay.
133 lines
4.3 KiB
C#
133 lines
4.3 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using osu.Framework.Allocation;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Extensions.ObjectExtensions;
|
|
using osu.Framework.Graphics;
|
|
using osu.Framework.Graphics.Containers;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Framework.Utils;
|
|
using osu.Game.Rulesets.Objects.Drawables;
|
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
|
using osu.Game.Screens.Play;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|
{
|
|
public partial class SpinnerRotationTracker : CircularContainer
|
|
{
|
|
public override bool IsPresent => true; // handle input when hidden
|
|
|
|
private readonly DrawableSpinner drawableSpinner;
|
|
|
|
private Vector2? mousePosition;
|
|
private float? lastAngle;
|
|
|
|
private float currentRotation;
|
|
private bool rotationTransferred;
|
|
|
|
[Resolved(canBeNull: true)]
|
|
private IGameplayClock? gameplayClock { get; set; }
|
|
|
|
public SpinnerRotationTracker(DrawableSpinner drawableSpinner)
|
|
{
|
|
this.drawableSpinner = drawableSpinner;
|
|
drawableSpinner.HitObjectApplied += resetState;
|
|
|
|
RelativeSizeAxes = Axes.Both;
|
|
}
|
|
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
|
|
|
public bool Tracking { get; set; }
|
|
|
|
/// <summary>
|
|
/// Whether the spinning is spinning at a reasonable speed to be considered visually spinning.
|
|
/// </summary>
|
|
public readonly BindableBool IsSpinning = new BindableBool();
|
|
|
|
/// <summary>
|
|
/// Whether currently in the correct time range to allow spinning.
|
|
/// </summary>
|
|
private bool isSpinnableTime => drawableSpinner.HitObject.StartTime <= Time.Current && drawableSpinner.HitObject.EndTime > Time.Current;
|
|
|
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
|
{
|
|
mousePosition = Parent!.ToLocalSpace(e.ScreenSpaceMousePosition);
|
|
return base.OnMouseMove(e);
|
|
}
|
|
|
|
protected override void Update()
|
|
{
|
|
base.Update();
|
|
|
|
if (mousePosition is Vector2 pos)
|
|
{
|
|
float thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(pos.X - DrawSize.X / 2, pos.Y - DrawSize.Y / 2));
|
|
float delta = lastAngle == null ? 0 : thisAngle - lastAngle.Value;
|
|
|
|
// Normalise the delta to -180 .. 180
|
|
if (delta > 180) delta -= 360;
|
|
if (delta < -180) delta += 360;
|
|
|
|
if (Tracking)
|
|
AddRotation(delta);
|
|
|
|
lastAngle = thisAngle;
|
|
}
|
|
|
|
IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation - Rotation) > 10f;
|
|
Rotation = (float)Interpolation.Damp(Rotation, currentRotation, 0.99, Math.Abs(Time.Elapsed));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotate the disc by the provided angle (in addition to any existing rotation).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Will be a no-op if not a valid time to spin.
|
|
/// </remarks>
|
|
/// <param name="delta">The delta angle.</param>
|
|
public void AddRotation(float delta)
|
|
{
|
|
if (!isSpinnableTime)
|
|
return;
|
|
|
|
if (!rotationTransferred)
|
|
{
|
|
currentRotation = Rotation;
|
|
rotationTransferred = true;
|
|
}
|
|
|
|
Debug.Assert(Math.Abs(delta) <= 180);
|
|
|
|
double rate = gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate;
|
|
delta = (float)(delta * Math.Abs(rate));
|
|
|
|
currentRotation += delta;
|
|
drawableSpinner.Result.History.ReportDelta(Time.Current, delta);
|
|
}
|
|
|
|
private void resetState(DrawableHitObject obj)
|
|
{
|
|
Tracking = false;
|
|
IsSpinning.Value = false;
|
|
mousePosition = null;
|
|
lastAngle = null;
|
|
currentRotation = 0;
|
|
Rotation = 0;
|
|
rotationTransferred = false;
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
base.Dispose(isDisposing);
|
|
|
|
if (drawableSpinner.IsNotNull())
|
|
drawableSpinner.HitObjectApplied -= resetState;
|
|
}
|
|
}
|
|
}
|