From ad2cd0ba8fb0b640155a704d2f39069eabec4985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 19 Jun 2024 13:18:56 +0200 Subject: [PATCH 1/2] Adjust behaviour of hit animations toggle to match user expectations --- .../Components/HitCircleOverlapMarker.cs | 9 ------- .../HitCircles/HitCircleSelectionBlueprint.cs | 24 +++++++++++++++++++ .../Blueprints/Sliders/SliderCircleOverlay.cs | 14 +++++------ .../Sliders/SliderSelectionBlueprint.cs | 14 ++++++++++- .../Objects/Drawables/DrawableHitCircle.cs | 23 ++++++++++++++++++ .../Objects/Drawables/DrawableSlider.cs | 18 ++++++++++++++ .../Objects/Drawables/DrawableSliderTail.cs | 24 +++++++++++++++++++ .../Objects/Drawables/DrawableHitObject.cs | 18 +++++++------- 8 files changed, 117 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs index fe335a048d..8ed9d0476a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCircleOverlapMarker.cs @@ -7,7 +7,6 @@ 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.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Objects.Types; @@ -16,7 +15,6 @@ using osu.Game.Rulesets.Osu.Skinning.Default; using osu.Game.Screens.Edit; using osu.Game.Skinning; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components { @@ -48,13 +46,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, ring = new RingPiece { BorderThickness = 4, diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index 0608f8c929..fd2bbe9916 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -16,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles protected readonly HitCirclePiece CirclePiece; private readonly HitCircleOverlapMarker marker; + private readonly Bindable showHitMarkers = new Bindable(); public HitCircleSelectionBlueprint(HitCircle circle) : base(circle) @@ -27,12 +31,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles }; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.EditorShowHitMarkers, showHitMarkers); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + showHitMarkers.BindValueChanged(_ => + { + if (!showHitMarkers.Value) + DrawableObject.RestoreHitAnimations(); + }); + } + protected override void Update() { base.Update(); CirclePiece.UpdateFrom(HitObject); marker.UpdateFrom(HitObject); + + if (showHitMarkers.Value) + DrawableObject.SuppressHitAnimations(); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs index d47cf6bf23..bd3b4bbc54 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleOverlay.cs @@ -1,7 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; @@ -14,18 +13,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly Slider slider; private readonly SliderPosition position; - private readonly HitCircleOverlapMarker marker; + private readonly HitCircleOverlapMarker? marker; public SliderCircleOverlay(Slider slider, SliderPosition position) { this.slider = slider; this.position = position; - InternalChildren = new Drawable[] - { - marker = new HitCircleOverlapMarker(), - CirclePiece = new HitCirclePiece(), - }; + if (position == SliderPosition.Start) + AddInternal(marker = new HitCircleOverlapMarker()); + + AddInternal(CirclePiece = new HitCirclePiece()); } protected override void Update() @@ -35,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var circle = position == SliderPosition.Start ? (HitCircle)slider.HeadCircle : slider.TailCircle; CirclePiece.UpdateFrom(circle); - marker.UpdateFrom(circle); + marker?.UpdateFrom(circle); } public override void Hide() diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 49fdf12d60..7ee6530099 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Audio; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -59,6 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private readonly BindableList controlPoints = new BindableList(); private readonly IBindable pathVersion = new Bindable(); private readonly BindableList selectedObjects = new BindableList(); + private readonly Bindable showHitMarkers = new Bindable(); public SliderSelectionBlueprint(Slider slider) : base(slider) @@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { InternalChildren = new Drawable[] { @@ -74,6 +76,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HeadOverlay = CreateCircleOverlay(HitObject, SliderPosition.Start), TailOverlay = CreateCircleOverlay(HitObject, SliderPosition.End), }; + + config.BindWith(OsuSetting.EditorShowHitMarkers, showHitMarkers); } protected override void LoadComplete() @@ -90,6 +94,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (editorBeatmap != null) selectedObjects.BindTo(editorBeatmap.SelectedHitObjects); selectedObjects.BindCollectionChanged((_, _) => updateVisualDefinition(), true); + showHitMarkers.BindValueChanged(_ => + { + if (!showHitMarkers.Value) + DrawableObject.RestoreHitAnimations(); + }); } public override bool HandleQuickDeletion() @@ -110,6 +119,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (IsSelected) BodyPiece.UpdateFrom(HitObject); + + if (showHitMarkers.Value) + DrawableObject.SuppressHitAnimations(); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index c3ce6acce9..26e9773967 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -319,5 +320,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } } + + #region FOR EDITOR USE ONLY, DO NOT USE FOR ANY OTHER PURPOSE + + internal void SuppressHitAnimations() + { + UpdateState(ArmedState.Idle); + UpdateComboColour(); + + using (BeginAbsoluteSequence(StateUpdateTime - 5)) + this.TransformBindableTo(AccentColour, Color4.White, Math.Max(0, HitStateUpdateTime - StateUpdateTime)); + + using (BeginAbsoluteSequence(HitStateUpdateTime)) + this.FadeOut(700).Expire(); + } + + internal void RestoreHitAnimations() + { + UpdateState(ArmedState.Hit, force: true); + UpdateComboColour(); + } + + #endregion } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index e519e51562..7bae3cefcf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -370,5 +370,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private partial class DefaultSliderBody : PlaySliderBody { } + + #region FOR EDITOR USE ONLY, DO NOT USE FOR ANY OTHER PURPOSE + + internal void SuppressHitAnimations() + { + UpdateState(ArmedState.Idle); + HeadCircle.SuppressHitAnimations(); + TailCircle.SuppressHitAnimations(); + } + + internal void RestoreHitAnimations() + { + UpdateState(ArmedState.Hit, force: true); + HeadCircle.RestoreHitAnimations(); + TailCircle.RestoreHitAnimations(); + } + + #endregion } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index c4731118a1..21aa672d10 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -125,5 +127,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (Slider != null) Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0); } + + #region FOR EDITOR USE ONLY, DO NOT USE FOR ANY OTHER PURPOSE + + internal void SuppressHitAnimations() + { + UpdateState(ArmedState.Idle); + UpdateComboColour(); + + using (BeginAbsoluteSequence(StateUpdateTime - 5)) + this.TransformBindableTo(AccentColour, Color4.White, Math.Max(0, HitStateUpdateTime - StateUpdateTime)); + + using (BeginAbsoluteSequence(HitStateUpdateTime)) + this.FadeOut(700).Expire(); + } + + internal void RestoreHitAnimations() + { + UpdateState(ArmedState.Hit); + UpdateComboColour(); + } + + #endregion } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 3ce6cc3cef..1f735576bc 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -314,11 +314,11 @@ namespace osu.Game.Rulesets.Objects.Drawables private void updateStateFromResult() { if (Result.IsHit) - updateState(ArmedState.Hit, true); + UpdateState(ArmedState.Hit, true); else if (Result.HasResult) - updateState(ArmedState.Miss, true); + UpdateState(ArmedState.Miss, true); else - updateState(ArmedState.Idle, true); + UpdateState(ArmedState.Idle, true); } protected sealed override void OnFree(HitObjectLifetimeEntry entry) @@ -402,7 +402,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void onRevertResult() { - updateState(ArmedState.Idle); + UpdateState(ArmedState.Idle); OnRevertResult?.Invoke(this, Result); } @@ -421,7 +421,7 @@ namespace osu.Game.Rulesets.Objects.Drawables if (Result is not null) { Result.TimeOffset = 0; - updateState(State.Value, true); + UpdateState(State.Value, true); } DefaultsApplied?.Invoke(this); @@ -461,7 +461,7 @@ namespace osu.Game.Rulesets.Objects.Drawables throw new InvalidOperationException( $"Should never clear a {nameof(DrawableHitObject)} as the base implementation adds components. If attempting to use {nameof(InternalChild)} or {nameof(InternalChildren)}, using {nameof(AddInternal)} or {nameof(AddRangeInternal)} instead."); - private void updateState(ArmedState newState, bool force = false) + protected void UpdateState(ArmedState newState, bool force = false) { if (State.Value == newState && !force) return; @@ -506,7 +506,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Reapplies the current . /// - public void RefreshStateTransforms() => updateState(State.Value, true); + public void RefreshStateTransforms() => UpdateState(State.Value, true); /// /// Apply (generally fade-in) transforms leading into the start time. @@ -565,7 +565,7 @@ namespace osu.Game.Rulesets.Objects.Drawables ApplySkin(CurrentSkin, true); if (IsLoaded) - updateState(State.Value, true); + UpdateState(State.Value, true); } protected void UpdateComboColour() @@ -725,7 +725,7 @@ namespace osu.Game.Rulesets.Objects.Drawables Result.GameplayRate = (Clock as IGameplayClock)?.GetTrueGameplayRate() ?? Clock.Rate; if (Result.HasResult) - updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); + UpdateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); OnNewResult?.Invoke(this, Result); } From df43a1c6ccf42aaf8a08d659d84b718bef58b826 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 23 Jun 2024 03:31:40 +0800 Subject: [PATCH 2/2] Add note about every-frame-transforms --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 26e9773967..6f419073eb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -328,6 +328,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables UpdateState(ArmedState.Idle); UpdateComboColour(); + // This method is called every frame. If we need to, the following can likely be converted + // to code which doesn't use transforms at all. + + // Matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338) using (BeginAbsoluteSequence(StateUpdateTime - 5)) this.TransformBindableTo(AccentColour, Color4.White, Math.Max(0, HitStateUpdateTime - StateUpdateTime));