Move catcher trail generation logic to Catcher

It resolves mutual dependency of `Catcher` and `CatcherTrailDisplay`.
Trail generation logic is moved to `Catcher`.
The generation logic no longer uses delayed scheduling because the hidden state is hard to manage.
Instead, the last time a trail is generated is calculated and used.
The new logic has a different behavior when the dash key is pressed in succession under 50ms, but it is not noticeable for normal plays.
This commit is contained in:
ekrctb
2021-07-26 17:46:56 +09:00
parent 9ae3c685db
commit bb046fa3b8
7 changed files with 56 additions and 93 deletions

View File

@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests
var skin = new TestSkin { FlipCatcherPlate = flip }; var skin = new TestSkin { FlipCatcherPlate = flip };
container.Child = new SkinProvidingContainer(skin) container.Child = new SkinProvidingContainer(skin)
{ {
Child = catcher = new Catcher(new Container(), new DroppedObjectContainer()) Child = catcher = new Catcher(new CatcherTrailDisplay(), new DroppedObjectContainer())
{ {
Anchor = Anchor.Centre Anchor = Anchor.Centre
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved] [Resolved]
private OsuConfigManager config { get; set; } private OsuConfigManager config { get; set; }
private Container trailContainer; private CatcherTrailDisplay trailDisplay;
private DroppedObjectContainer droppedObjectContainer; private DroppedObjectContainer droppedObjectContainer;
@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
CircleSize = 0, CircleSize = 0,
}; };
trailContainer = new Container(); trailDisplay = new CatcherTrailDisplay();
droppedObjectContainer = new DroppedObjectContainer(); droppedObjectContainer = new DroppedObjectContainer();
Child = new Container Child = new Container
@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Catch.Tests
Children = new Drawable[] Children = new Drawable[]
{ {
droppedObjectContainer, droppedObjectContainer,
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty), catcher = new TestCatcher(trailDisplay, droppedObjectContainer, difficulty),
trailContainer, trailDisplay,
} }
}; };
}); });
@ -294,8 +294,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>(); public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) public TestCatcher(CatcherTrailDisplay trails, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty)
: base(trailsTarget, droppedObjectTarget, difficulty) : base(trails, droppedObjectTarget, difficulty)
{ {
} }
} }

View File

@ -121,11 +121,13 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
{ {
var droppedObjectContainer = new DroppedObjectContainer(); var trailDisplay = new CatcherTrailDisplay { Depth = -1 };
Add(trailDisplay);
var droppedObjectContainer = new DroppedObjectContainer();
Add(droppedObjectContainer); Add(droppedObjectContainer);
Catcher = new Catcher(this, droppedObjectContainer, beatmapDifficulty) Catcher = new Catcher(trailDisplay, droppedObjectContainer, beatmapDifficulty)
{ {
X = CatchPlayfield.CENTER_X X = CatchPlayfield.CENTER_X
}; };

View File

@ -113,30 +113,28 @@ namespace osu.Game.Rulesets.Catch.Tests
private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null) private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
{ {
Container trailsContainer = null;
Catcher catcher = null;
CatcherTrailDisplay trails = null; CatcherTrailDisplay trails = null;
Catcher catcher = null;
AddStep("create hyper-dashing catcher", () => AddStep("create hyper-dashing catcher", () =>
{ {
trailsContainer = new Container(); trails = new CatcherTrailDisplay();
Child = setupSkinHierarchy(new Container Child = setupSkinHierarchy(new Container
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Children = new Drawable[] Children = new Drawable[]
{ {
catcher = new Catcher(trailsContainer, new DroppedObjectContainer()) catcher = new Catcher(trails, new DroppedObjectContainer())
{ {
Scale = new Vector2(4) Scale = new Vector2(4)
}, },
trailsContainer trails
} }
}, skin); }, skin);
}); });
AddStep("get trails container", () => AddStep("start hyper-dash", () =>
{ {
trails = trailsContainer.OfType<CatcherTrailDisplay>().Single();
catcher.SetHyperDashState(2); catcher.SetHyperDashState(2);
}); });

View File

@ -3,7 +3,6 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Objects.Drawables;
@ -45,14 +44,14 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
var trailContainer = new Container var trailDisplay = new CatcherTrailDisplay
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft Origin = Anchor.TopLeft
}; };
var droppedObjectContainer = new DroppedObjectContainer(); var droppedObjectContainer = new DroppedObjectContainer();
Catcher = new Catcher(trailContainer, droppedObjectContainer, difficulty) Catcher = new Catcher(trailDisplay, droppedObjectContainer, difficulty)
{ {
X = CENTER_X X = CENTER_X
}; };
@ -70,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
Catcher = Catcher, Catcher = Catcher,
}, },
trailContainer, trailDisplay,
HitObjectContainer, HitObjectContainer,
}); });

View File

@ -71,10 +71,10 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
private const float caught_fruit_scale_adjust = 0.5f; private const float caught_fruit_scale_adjust = 0.5f;
[NotNull] /// <summary>
private readonly Container trailsTarget; /// Contains trails and afterimages (also called "end glow" in code) of the catcher.
/// </summary>
private CatcherTrailDisplay trails; private readonly CatcherTrailDisplay trails;
/// <summary> /// <summary>
/// Contains caught objects on the plate. /// Contains caught objects on the plate.
@ -92,20 +92,7 @@ namespace osu.Game.Rulesets.Catch.UI
private set => Body.AnimationState.Value = value; private set => Body.AnimationState.Value = value;
} }
private bool dashing; public bool Dashing { get; set; }
public bool Dashing
{
get => dashing;
set
{
if (value == dashing) return;
dashing = value;
updateTrailVisibility();
}
}
/// <summary> /// <summary>
/// The currently facing direction. /// The currently facing direction.
@ -138,9 +125,9 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool<CaughtBanana> caughtBananaPool; private readonly DrawablePool<CaughtBanana> caughtBananaPool;
private readonly DrawablePool<CaughtDroplet> caughtDropletPool; private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) public Catcher([NotNull] CatcherTrailDisplay trails, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null)
{ {
this.trailsTarget = trailsTarget; this.trails = trails;
this.droppedObjectTarget = droppedObjectTarget; this.droppedObjectTarget = droppedObjectTarget;
Origin = Anchor.TopCentre; Origin = Anchor.TopCentre;
@ -177,15 +164,6 @@ namespace osu.Game.Rulesets.Catch.UI
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting); hitLighting = config.GetBindable<bool>(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);
} }
/// <summary> /// <summary>
@ -313,7 +291,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (!wasHyperDashing) if (!wasHyperDashing)
{ {
trails.DisplayEndGlow(); trails.DisplayEndGlow(CurrentState, X, Scale * Body.Scale);
runHyperDashStateTransition(true); runHyperDashStateTransition(true);
} }
} }
@ -331,13 +309,9 @@ namespace osu.Game.Rulesets.Catch.UI
private void runHyperDashStateTransition(bool hyperDashing) private void runHyperDashStateTransition(bool hyperDashing)
{ {
updateTrailVisibility();
this.FadeColour(hyperDashing ? hyperDashColour : Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint); 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) protected override void SkinChanged(ISkinSource skin)
{ {
base.SkinChanged(skin); base.SkinChanged(skin);
@ -373,6 +347,15 @@ namespace osu.Game.Rulesets.Catch.UI
X = hyperDashTargetPosition; X = hyperDashTargetPosition;
SetHyperDashState(); SetHyperDashState();
} }
if (Dashing || HyperDashing)
{
double lastTrailTime = trails.LastDashTrail?.LifetimeStart ?? double.NegativeInfinity;
double generationInterval = HyperDashing ? 25 : 50;
if (Time.Current - lastTrailTime >= generationInterval)
trails.DisplayDashTrail(CurrentState, X, Scale * Body.Scale, HyperDashing);
}
} }
private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position) private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)

View File

@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -17,7 +17,10 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary> /// </summary>
public class CatcherTrailDisplay : CompositeDrawable public class CatcherTrailDisplay : CompositeDrawable
{ {
private readonly Catcher catcher; [CanBeNull]
public CatcherTrail LastDashTrail => dashTrails.Concat(hyperDashTrails)
.OrderByDescending(trail => trail.LifetimeStart)
.FirstOrDefault();
private readonly DrawablePool<CatcherTrail> trailPool; private readonly DrawablePool<CatcherTrail> trailPool;
@ -55,30 +58,8 @@ namespace osu.Game.Rulesets.Catch.UI
} }
} }
private bool trail; public CatcherTrailDisplay()
/// <summary>
/// Whether to start displaying trails following the catcher.
/// </summary>
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; RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
@ -93,9 +74,11 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary> /// <summary>
/// Displays a single end-glow catcher sprite. /// Displays a single end-glow catcher sprite.
/// </summary> /// </summary>
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.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In); endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In);
@ -103,28 +86,26 @@ namespace osu.Game.Rulesets.Catch.UI
endGlow.Expire(true); endGlow.Expire(true);
} }
private void displayTrail() public void DisplayDashTrail(CatcherAnimationState animationState, float x, Vector2 scale, bool hyperDashing)
{ {
if (!DisplayTrail) var sprite = createTrail(animationState, x, scale);
return;
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.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
sprite.Expire(true); sprite.Expire(true);
Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
} }
private CatcherTrail createTrailSprite(Container<CatcherTrail> target) private CatcherTrail createTrail(CatcherAnimationState animationState, float x, Vector2 scale)
{ {
CatcherTrail sprite = trailPool.Get(); CatcherTrail sprite = trailPool.Get();
sprite.AnimationState = catcher.CurrentState; sprite.AnimationState = animationState;
sprite.Scale = catcher.Scale * catcher.Body.Scale; sprite.Scale = scale;
sprite.Position = catcher.Position; sprite.Position = new Vector2(x, 0);
target.Add(sprite);
return sprite; return sprite;
} }