diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index b77d4addee..aaace89cd5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -32,37 +30,42 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } - protected override void OnTrackingChanged(ValueChangedEvent tracking) + protected override void OnSliderPress() { - Debug.Assert(ParentObject != null); - const float duration = 300f; - if (ParentObject.Judged) - return; + if (Precision.AlmostEquals(0, Alpha)) + this.ScaleTo(1); - if (tracking.NewValue) - { - if (Precision.AlmostEquals(0, Alpha)) - this.ScaleTo(1); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) + .FadeIn(duration, Easing.OutQuint); + } - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) - .FadeTo(1f, duration, Easing.OutQuint); - } - else - { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration / 2, Easing.OutQuint) - .FadeTo(0, duration / 2, Easing.OutQuint); - } + protected override void OnSliderRelease() + { + const float duration = 150; + + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint) + .FadeTo(0, duration, Easing.OutQuint); } protected override void OnSliderEnd() { - const float fade_duration = 300; + const float duration = 300; - // intentionally pile on an extra FadeOut to make it happen much faster - this.ScaleTo(1, fade_duration, Easing.OutQuint); - this.FadeOut(fade_duration / 2, Easing.OutQuint); + this.ScaleTo(1, duration, Easing.OutQuint) + .FadeOut(duration / 2, Easing.OutQuint); + } + + protected override void OnSliderTick() + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); + } + + protected override void OnSliderBreak() + { } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 321705d25e..9eb8e66c83 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -23,7 +23,17 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load() { - ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true); + ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(tracking => + { + Debug.Assert(ParentObject != null); + if (ParentObject.Judged) + return; + + if (tracking.NewValue) + OnSliderPress(); + else + OnSliderRelease(); + }, true); } protected override void LoadComplete() @@ -48,13 +58,46 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - // Gets called by slider ticks, tails, etc., leading to duplicated - // animations which may negatively affect performance - if (drawableObject is not DrawableSlider) - return; + Debug.Assert(ParentObject != null); - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - OnSliderEnd(); + switch (state) + { + case ArmedState.Hit: + switch (drawableObject) + { + case DrawableSliderTail: + // Use ParentObject instead of drawableObject because slider tail's + // HitStateUpdateTime is ~36ms before the actual slider end (aka slider + // tail leniency) + using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime)) + OnSliderEnd(); + break; + + case DrawableSliderTick: + case DrawableSliderRepeat: + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderTick(); + break; + } + + break; + + case ArmedState.Miss: + switch (drawableObject) + { + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + // Despite above comment, ok to use drawableObject.HitStateUpdateTime + // here, since on stable, the break anim plays right when the tail is + // missed, not when the slider ends + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderBreak(); + break; + } + + break; + } } protected override void Dispose(bool isDisposing) @@ -68,8 +111,14 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - protected abstract void OnTrackingChanged(ValueChangedEvent tracking); + protected abstract void OnSliderPress(); + + protected abstract void OnSliderRelease(); protected abstract void OnSliderEnd(); + + protected abstract void OnSliderTick(); + + protected abstract void OnSliderBreak(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 5b7da5a1ba..0d12fb01f5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using osu.Framework.Bindables; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy @@ -21,29 +20,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChild = animationContent; } - protected override void OnTrackingChanged(ValueChangedEvent tracking) + protected override void OnSliderPress() { Debug.Assert(ParentObject != null); - if (ParentObject.Judged) - return; - double remainingTime = Math.Max(0, ParentObject.HitStateUpdateTime - Time.Current); // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this). - if (tracking.NewValue) - { - // TODO: Follow circle should bounce on each slider tick. - this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) - .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); - } - else - { - // TODO: Should animate only at the next slider tick if we want to match stable perfectly. - this.ScaleTo(4f, 100) - .FadeTo(0f, 100); - } + this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) + .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); + } + + protected override void OnSliderRelease() + { } protected override void OnSliderEnd() @@ -51,5 +41,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy this.ScaleTo(1.6f, 200, Easing.Out) .FadeOut(200, Easing.In); } + + protected override void OnSliderTick() + { + this.ScaleTo(2.2f) + .ScaleTo(2f, 200); + } + + protected override void OnSliderBreak() + { + this.ScaleTo(4f, 100) + .FadeTo(0f, 100); + } } }