diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs index 83f28086e6..8ae2bcca0e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchSkinConfiguration.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests var skin = new TestSkin { FlipCatcherPlate = flip }; container.Child = new SkinProvidingContainer(skin) { - Child = catcher = new Catcher(new Container(), new DroppedObjectContainer()) + Child = catcher = new Catcher(new DroppedObjectContainer()) { Anchor = Anchor.Centre } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index b4282e6784..540f02580f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -31,8 +31,6 @@ namespace osu.Game.Rulesets.Catch.Tests [Resolved] private OsuConfigManager config { get; set; } - private Container trailContainer; - private DroppedObjectContainer droppedObjectContainer; private TestCatcher catcher; @@ -45,7 +43,6 @@ namespace osu.Game.Rulesets.Catch.Tests CircleSize = 0, }; - trailContainer = new Container(); droppedObjectContainer = new DroppedObjectContainer(); Child = new Container @@ -54,8 +51,7 @@ namespace osu.Game.Rulesets.Catch.Tests Children = new Drawable[] { droppedObjectContainer, - catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty), - trailContainer, + catcher = new TestCatcher(droppedObjectContainer, difficulty), } }; }); @@ -294,8 +290,8 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) - : base(trailsTarget, droppedObjectTarget, difficulty) + public TestCatcher(DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) + : base(droppedObjectTarget, difficulty) { } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 6a518cf0ef..a3307c9224 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -122,10 +122,9 @@ namespace osu.Game.Rulesets.Catch.Tests public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) { var droppedObjectContainer = new DroppedObjectContainer(); - Add(droppedObjectContainer); - Catcher = new Catcher(this, droppedObjectContainer, beatmapDifficulty) + Catcher = new Catcher(droppedObjectContainer, beatmapDifficulty) { X = CatchPlayfield.CENTER_X }; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs index 73797d0a6a..82b69716a8 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs @@ -113,30 +113,28 @@ namespace osu.Game.Rulesets.Catch.Tests private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null) { - Container trailsContainer = null; - Catcher catcher = null; CatcherTrailDisplay trails = null; + Catcher catcher = null; AddStep("create hyper-dashing catcher", () => { - trailsContainer = new Container(); + CatcherArea catcherArea; Child = setupSkinHierarchy(new Container { Anchor = Anchor.Centre, - Children = new Drawable[] + Child = catcherArea = new CatcherArea { - catcher = new Catcher(trailsContainer, new DroppedObjectContainer()) + Catcher = catcher = new Catcher(new DroppedObjectContainer()) { Scale = new Vector2(4) - }, - trailsContainer + } } }, skin); + trails = catcherArea.ChildrenOfType().Single(); }); - AddStep("get trails container", () => + AddStep("start hyper-dash", () => { - trails = trailsContainer.OfType().Single(); catcher.SetHyperDashState(2); }); diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index b43815a8bd..1e20643a08 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -45,14 +44,9 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - var trailContainer = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft - }; var droppedObjectContainer = new DroppedObjectContainer(); - Catcher = new Catcher(trailContainer, droppedObjectContainer, difficulty) + Catcher = new Catcher(droppedObjectContainer, difficulty) { X = CENTER_X }; @@ -70,7 +64,6 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.TopLeft, Catcher = Catcher, }, - trailContainer, HitObjectContainer, }); diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index b724b84a56..b42e5352dd 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -71,11 +71,6 @@ namespace osu.Game.Rulesets.Catch.UI /// private const float caught_fruit_scale_adjust = 0.5f; - [NotNull] - private readonly Container trailsTarget; - - private CatcherTrailDisplay trails; - /// /// Contains caught objects on the plate. /// @@ -88,30 +83,22 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherAnimationState CurrentState { - get => Body.AnimationState.Value; - private set => Body.AnimationState.Value = value; + get => body.AnimationState.Value; + private set => body.AnimationState.Value = value; } - private bool dashing; - - public bool Dashing - { - get => dashing; - set - { - if (value == dashing) return; - - dashing = value; - - updateTrailVisibility(); - } - } + /// + /// Whether the catcher is currently dashing. + /// + public bool Dashing { get; set; } /// /// The currently facing direction. /// public Direction VisualDirection { get; set; } = Direction.Right; + public Vector2 BodyScale => Scale * body.Scale; + /// /// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed. /// @@ -122,10 +109,9 @@ namespace osu.Game.Rulesets.Catch.UI /// private readonly float catchWidth; - internal readonly SkinnableCatcher Body; + private readonly SkinnableCatcher body; private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR; - private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR; private double hyperDashModifier = 1; private int hyperDashDirection; @@ -138,9 +124,8 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) { - this.trailsTarget = trailsTarget; this.droppedObjectTarget = droppedObjectTarget; Origin = Anchor.TopCentre; @@ -164,7 +149,7 @@ namespace osu.Game.Rulesets.Catch.UI // offset fruit vertically to better place "above" the plate. Y = -5 }, - Body = new SkinnableCatcher(), + body = new SkinnableCatcher(), hitExplosionContainer = new HitExplosionContainer { Anchor = Anchor.TopCentre, @@ -177,15 +162,6 @@ namespace osu.Game.Rulesets.Catch.UI private void load(OsuConfigManager config) { hitLighting = config.GetBindable(OsuSetting.HitLighting); - trails = new CatcherTrailDisplay(this); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - // don't add in above load as we may potentially modify a parent in an unsafe manner. - trailsTarget.Add(trails); } /// @@ -307,10 +283,7 @@ namespace osu.Game.Rulesets.Catch.UI hyperDashTargetPosition = targetPosition; if (!wasHyperDashing) - { - trails.DisplayEndGlow(); runHyperDashStateTransition(true); - } } } @@ -326,13 +299,9 @@ namespace osu.Game.Rulesets.Catch.UI private void runHyperDashStateTransition(bool hyperDashing) { - updateTrailVisibility(); - this.FadeColour(hyperDashing ? hyperDashColour : Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); } - private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing; - protected override void SkinChanged(ISkinSource skin) { base.SkinChanged(skin); @@ -341,13 +310,6 @@ namespace osu.Game.Rulesets.Catch.UI skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? DEFAULT_HYPER_DASH_COLOUR; - hyperDashEndGlowColour = - skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value ?? - hyperDashColour; - - trails.HyperDashTrailsColour = hyperDashColour; - trails.EndGlowSpritesColour = hyperDashEndGlowColour; - flipCatcherPlate = skin.GetConfig(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true; runHyperDashStateTransition(HyperDashing); @@ -358,7 +320,7 @@ namespace osu.Game.Rulesets.Catch.UI base.Update(); var scaleFromDirection = new Vector2((int)VisualDirection, 1); - Body.Scale = scaleFromDirection; + body.Scale = scaleFromDirection; caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One; // Correct overshooting. diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index de0ace9817..2d6d7fb8d8 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -25,17 +25,15 @@ namespace osu.Game.Rulesets.Catch.UI public Catcher Catcher { get => catcher; - set - { - if (catcher != null) - Remove(catcher); - - Add(catcher = value); - } + set => catcherContainer.Child = catcher = value; } + private readonly Container catcherContainer; + private readonly CatchComboDisplay comboDisplay; + private readonly CatcherTrailDisplay catcherTrails; + private Catcher catcher; /// @@ -45,20 +43,28 @@ namespace osu.Game.Rulesets.Catch.UI /// private int currentDirection; + // TODO: support replay rewind + private bool lastHyperDashState; + /// /// must be set before loading. /// public CatcherArea() { Size = new Vector2(CatchPlayfield.WIDTH, Catcher.BASE_SIZE); - Child = comboDisplay = new CatchComboDisplay + Children = new Drawable[] { - RelativeSizeAxes = Axes.None, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopLeft, - Origin = Anchor.Centre, - Margin = new MarginPadding { Bottom = 350f }, - X = CatchPlayfield.CENTER_X + catcherContainer = new Container { RelativeSizeAxes = Axes.Both }, + catcherTrails = new CatcherTrailDisplay(), + comboDisplay = new CatchComboDisplay + { + RelativeSizeAxes = Axes.None, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopLeft, + Origin = Anchor.Centre, + Margin = new MarginPadding { Bottom = 350f }, + X = CatchPlayfield.CENTER_X + } }; } @@ -102,6 +108,19 @@ namespace osu.Game.Rulesets.Catch.UI base.UpdateAfterChildren(); comboDisplay.X = Catcher.X; + + if (!lastHyperDashState && Catcher.HyperDashing && Time.Elapsed > 0) + catcherTrails.DisplayEndGlow(Catcher.CurrentState, Catcher.X, Catcher.BodyScale); + + if (Catcher.Dashing || Catcher.HyperDashing) + { + double generationInterval = Catcher.HyperDashing ? 25 : 50; + + if (Time.Current - catcherTrails.LastDashTrailTime >= generationInterval) + catcherTrails.DisplayDashTrail(Catcher.CurrentState, Catcher.X, Catcher.BodyScale, Catcher.HyperDashing); + } + + lastHyperDashState = Catcher.HyperDashing; } public void SetCatcherPosition(float X) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs index b59fabcb70..31b7a6e0ed 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs @@ -2,10 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Catch.Skinning; +using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -15,9 +16,18 @@ namespace osu.Game.Rulesets.Catch.UI /// Represents a component responsible for displaying /// the appropriate catcher trails when requested to. /// - public class CatcherTrailDisplay : CompositeDrawable + public class CatcherTrailDisplay : SkinReloadableDrawable { - private readonly Catcher catcher; + /// + /// The most recent time a dash trail was added to this container. + /// Only alive (not faded out) trails are considered. + /// Returns if no dash trail is alive. + /// + public double LastDashTrailTime => getLastDashTrailTime(); + + public Color4 HyperDashTrailsColour => hyperDashTrails.Colour; + + public Color4 EndGlowSpritesColour => endGlowSprites.Colour; private readonly DrawablePool trailPool; @@ -25,60 +35,8 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Container hyperDashTrails; private readonly Container endGlowSprites; - private Color4 hyperDashTrailsColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; - - public Color4 HyperDashTrailsColour + public CatcherTrailDisplay() { - get => hyperDashTrailsColour; - set - { - if (hyperDashTrailsColour == value) - return; - - hyperDashTrailsColour = value; - hyperDashTrails.Colour = hyperDashTrailsColour; - } - } - - private Color4 endGlowSpritesColour = Catcher.DEFAULT_HYPER_DASH_COLOUR; - - public Color4 EndGlowSpritesColour - { - get => endGlowSpritesColour; - set - { - if (endGlowSpritesColour == value) - return; - - endGlowSpritesColour = value; - endGlowSprites.Colour = endGlowSpritesColour; - } - } - - private bool trail; - - /// - /// Whether to start displaying trails following the catcher. - /// - public bool DisplayTrail - { - get => trail; - set - { - if (trail == value) - return; - - trail = value; - - if (trail) - displayTrail(); - } - } - - public CatcherTrailDisplay([NotNull] Catcher catcher) - { - this.catcher = catcher ?? throw new ArgumentNullException(nameof(catcher)); - RelativeSizeAxes = Axes.Both; InternalChildren = new Drawable[] @@ -90,12 +48,22 @@ namespace osu.Game.Rulesets.Catch.UI }; } + protected override void SkinChanged(ISkinSource skin) + { + base.SkinChanged(skin); + + hyperDashTrails.Colour = skin.GetConfig(CatchSkinColour.HyperDash)?.Value ?? Catcher.DEFAULT_HYPER_DASH_COLOUR; + endGlowSprites.Colour = skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value ?? hyperDashTrails.Colour; + } + /// /// Displays a single end-glow catcher sprite. /// - public void DisplayEndGlow() + public void DisplayEndGlow(CatcherAnimationState animationState, float x, Vector2 scale) { - var endGlow = createTrailSprite(endGlowSprites); + var endGlow = createTrail(animationState, x, scale); + + endGlowSprites.Add(endGlow); endGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In); endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In); @@ -103,30 +71,41 @@ namespace osu.Game.Rulesets.Catch.UI endGlow.Expire(true); } - private void displayTrail() + public void DisplayDashTrail(CatcherAnimationState animationState, float x, Vector2 scale, bool hyperDashing) { - if (!DisplayTrail) - return; + var sprite = createTrail(animationState, x, scale); - var sprite = createTrailSprite(catcher.HyperDashing ? hyperDashTrails : dashTrails); + if (hyperDashing) + hyperDashTrails.Add(sprite); + else + dashTrails.Add(sprite); sprite.FadeTo(0.4f).FadeOut(800, Easing.OutQuint); sprite.Expire(true); - - Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50); } - private CatcherTrail createTrailSprite(Container target) + private CatcherTrail createTrail(CatcherAnimationState animationState, float x, Vector2 scale) { CatcherTrail sprite = trailPool.Get(); - sprite.AnimationState = catcher.CurrentState; - sprite.Scale = catcher.Scale * catcher.Body.Scale; - sprite.Position = catcher.Position; - - target.Add(sprite); + sprite.AnimationState = animationState; + sprite.Scale = scale; + sprite.Position = new Vector2(x, 0); return sprite; } + + private double getLastDashTrailTime() + { + double maxTime = double.NegativeInfinity; + + foreach (var trail in dashTrails) + maxTime = Math.Max(maxTime, trail.LifetimeStart); + + foreach (var trail in hyperDashTrails) + maxTime = Math.Max(maxTime, trail.LifetimeStart); + + return maxTime; + } } }