From f37ba49f7f624614b99ddfadc5e54785d0722b5b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 3 Aug 2020 22:13:02 +0300 Subject: [PATCH] Add catch-specific combo counter with its legacy design --- .../Skinning/LegacyComboCounter.cs | 139 ++++++++++++++++++ .../UI/CatchComboDisplay.cs | 59 ++++++++ osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 32 +++- .../UI/ICatchComboCounter.cs | 34 +++++ 4 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs create mode 100644 osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs create mode 100644 osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs new file mode 100644 index 0000000000..9700bd0e08 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Skinning/LegacyComboCounter.cs @@ -0,0 +1,139 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Screens.Play; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.Skinning +{ + internal class LegacyComboCounter : CompositeDrawable, ICatchComboCounter + { + private readonly ISkin skin; + + private readonly string fontName; + private readonly float fontOverlap; + + private readonly LegacyRollingCounter counter; + private LegacyRollingCounter lastExplosion; + + public LegacyComboCounter(ISkin skin, string fontName, float fontOverlap) + { + this.skin = skin; + + this.fontName = fontName; + this.fontOverlap = fontOverlap; + + AutoSizeAxes = Axes.Both; + + Alpha = 0f; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Scale = new Vector2(0.8f); + + InternalChild = counter = new LegacyRollingCounter(skin, fontName, fontOverlap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + public void DisplayInitialCombo(int combo) => updateCombo(combo, null, true); + public void UpdateCombo(int combo, Color4? hitObjectColour) => updateCombo(combo, hitObjectColour, false); + + private void updateCombo(int combo, Color4? hitObjectColour, bool immediate) + { + // Combo fell to zero, roll down and fade out the counter. + if (combo == 0) + { + counter.Current.Value = 0; + if (lastExplosion != null) + lastExplosion.Current.Value = 0; + + this.FadeOut(immediate ? 0.0 : 400.0, Easing.Out); + return; + } + + // There may still be previous transforms being applied, finish them and remove explosion. + FinishTransforms(true); + if (lastExplosion != null) + RemoveInternal(lastExplosion); + + this.FadeIn().Delay(1000.0).FadeOut(300.0); + + // For simplicity, in the case of rewinding we'll just set the counter to the current combo value. + immediate |= Time.Elapsed < 0; + + if (immediate) + { + counter.SetCountWithoutRolling(combo); + return; + } + + counter.ScaleTo(1.5f).ScaleTo(0.8f, 250.0, Easing.Out) + .OnComplete(c => c.SetCountWithoutRolling(combo)); + + counter.Delay(250.0).ScaleTo(1f).ScaleTo(1.1f, 60.0).Then().ScaleTo(1f, 30.0); + + var explosion = new LegacyRollingCounter(skin, fontName, fontOverlap) + { + Alpha = 0.65f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.5f), + Colour = hitObjectColour ?? Color4.White, + Depth = 1f, + }; + + AddInternal(explosion); + + explosion.SetCountWithoutRolling(combo); + explosion.ScaleTo(1.9f, 400.0, Easing.Out) + .FadeOut(400.0) + .Expire(true); + + lastExplosion = explosion; + } + + private class LegacyRollingCounter : RollingCounter + { + private readonly ISkin skin; + + private readonly string fontName; + private readonly float fontOverlap; + + protected override bool IsRollingProportional => true; + + public LegacyRollingCounter(ISkin skin, string fontName, float fontOverlap) + { + this.skin = skin; + this.fontName = fontName; + this.fontOverlap = fontOverlap; + } + + public override void Increment(int amount) => Current.Value += amount; + + protected override double GetProportionalDuration(int currentValue, int newValue) + { + return Math.Abs(newValue - currentValue) * 75.0; + } + + protected override OsuSpriteText CreateSpriteText() + { + return new LegacySpriteText(skin, fontName) + { + Spacing = new Vector2(-fontOverlap, 0f) + }; + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs new file mode 100644 index 0000000000..10aee2ea31 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatchComboDisplay : SkinnableDrawable + { + private int currentCombo; + + [CanBeNull] + public ICatchComboCounter ComboCounter => Drawable as ICatchComboCounter; + + public CatchComboDisplay() + : base(new CatchSkinComponent(CatchSkinComponents.CatchComboCounter), _ => Empty()) + { + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + ComboCounter?.DisplayInitialCombo(currentCombo); + } + + public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result) + { + if (!result.Judgement.AffectsCombo || !result.HasResult) + return; + + if (result.Type == HitResult.Miss) + { + updateCombo(0, null); + return; + } + + updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value); + } + + public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result) + { + if (!result.Judgement.AffectsCombo || !result.HasResult) + return; + + updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); + } + + private void updateCombo(int newCombo, Color4? hitObjectColour) + { + currentCombo = newCombo; + ComboCounter?.UpdateCombo(newCombo, hitObjectColour); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 154e1576db..b5c040f80d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -28,6 +28,7 @@ public class CatchPlayfield : ScrollingPlayfield public const float CENTER_X = WIDTH / 2; internal readonly CatcherArea CatcherArea; + private readonly CatchComboDisplay comboDisplay; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => // only check the X position; handle all vertical space. @@ -48,12 +49,22 @@ public CatchPlayfield(BeatmapDifficulty difficulty, Func CatcherArea.OnResult((DrawableCatchHitObject)judgedObject, result); + { + var catchObject = (DrawableCatchHitObject)judgedObject; + CatcherArea.OnResult(catchObject, result); + + comboDisplay.OnNewResult(catchObject, result); + } + + private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result) + { + comboDisplay.OnRevertResult((DrawableCatchHitObject)judgedObject, result); + } } } diff --git a/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs new file mode 100644 index 0000000000..1363ed1352 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/ICatchComboCounter.cs @@ -0,0 +1,34 @@ +// 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 osuTK.Graphics; + +namespace osu.Game.Rulesets.Catch.UI +{ + /// + /// An interface providing a set of methods to update the combo counter. + /// + public interface ICatchComboCounter : IDrawable + { + /// + /// Updates the counter to display the provided as initial value. + /// The value should be immediately displayed without any animation. + /// + /// + /// This is required for when instantiating a combo counter in middle of accumulating combo (via skin change). + /// + /// The combo value to be displayed as initial. + void DisplayInitialCombo(int combo); + + /// + /// Updates the counter to animate a transition from the old combo value it had to the current provided one. + /// + /// + /// This is called regardless of whether the clock is rewinding. + /// + /// The new combo value. + /// The colour of the object if hit, null on miss. + void UpdateCombo(int combo, Color4? hitObjectColour); + } +}