1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 18:52:55 +08:00

Refactor fruit dropping code

- The repeated `Remove` call was quadratic complexity.
  Now it is linear time.
This commit is contained in:
ekrctb 2020-12-02 21:23:34 +09:00
parent 873f2363c1
commit 2eb2c934cc
4 changed files with 65 additions and 64 deletions

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[BackgroundDependencyLoader]
private void load()
{
SetContents(() => new Catcher(new Container())
SetContents(() => new Catcher(new Container(), new Container())
{
RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre,

View File

@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Catch.UI
CatcherArea = new CatcherArea(difficulty)
{
ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
};

View File

@ -17,7 +17,6 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@ -47,15 +46,15 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
public const double BASE_SPEED = 1.0;
public Container ExplodingFruitTarget;
private readonly Container<DrawablePalpableCatchHitObject> caughtFruitContainer;
[NotNull]
private readonly Container trailsTarget;
private CatcherTrailDisplay trails;
private readonly Container droppedObjectTarget;
private readonly Container<DrawablePalpableCatchHitObject> caughtFruitContainer;
public CatcherAnimationState CurrentState { get; private set; }
/// <summary>
@ -107,9 +106,10 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool<HitExplosion> hitExplosionPool;
private readonly Container<HitExplosion> hitExplosionContainer;
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null)
{
this.trailsTarget = trailsTarget;
this.droppedObjectTarget = droppedObjectTarget;
Origin = Anchor.TopCentre;
@ -369,41 +369,14 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary>
/// Drop any fruit off the plate.
/// </summary>
public void Drop()
{
foreach (var f in caughtFruitContainer.ToArray())
Drop(f);
}
public void Drop() => clearPlate(DroppedObjectAnimation.Drop);
/// <summary>
/// Explode any fruit off the plate.
/// </summary>
public void Explode()
{
foreach (var f in caughtFruitContainer.ToArray())
Explode(f);
}
public void Explode() => clearPlate(DroppedObjectAnimation.Explode);
public void Drop(DrawablePalpableCatchHitObject fruit)
{
removeFromPlateWithTransform(fruit, f =>
{
f.MoveToY(f.Y + 75, 750, Easing.InSine);
f.FadeOut(750);
});
}
public void Explode(DrawablePalpableCatchHitObject fruit)
{
var originalX = fruit.X * Scale.X;
removeFromPlateWithTransform(fruit, f =>
{
f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
});
}
public void Explode(DrawablePalpableCatchHitObject caughtObject) => removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
@ -477,33 +450,67 @@ namespace osu.Game.Rulesets.Catch.UI
updateCatcher();
}
private void removeFromPlateWithTransform(DrawablePalpableCatchHitObject fruit, Action<DrawablePalpableCatchHitObject> action)
private void clearPlate(DroppedObjectAnimation animation)
{
if (ExplodingFruitTarget != null)
var caughtObjects = caughtFruitContainer.Children.ToArray();
caughtFruitContainer.Clear(false);
droppedObjectTarget.AddRange(caughtObjects);
foreach (var caughtObject in caughtObjects)
drop(caughtObject, animation);
}
private void removeFromPlate(DrawablePalpableCatchHitObject caughtObject, DroppedObjectAnimation animation)
{
if (!caughtFruitContainer.Remove(caughtObject))
throw new InvalidOperationException("Can only drop a caught object on the plate");
droppedObjectTarget.Add(caughtObject);
drop(caughtObject, animation);
}
private void drop(Drawable d, DroppedObjectAnimation animation)
{
var originalX = d.X * Scale.X;
d.Anchor = Anchor.TopLeft;
d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget);
animate(d, animation, originalX);
}
private void animate(Drawable d, DroppedObjectAnimation animation, float originalX)
{
// temporary hack to make sure transforms are not cleared by DHO state update
if (!d.IsLoaded)
{
fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
if (!caughtFruitContainer.Remove(fruit))
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
return;
ExplodingFruitTarget.Add(fruit);
d.OnLoadComplete += _ => animate(d, animation, originalX);
return;
}
var actionTime = Clock.CurrentTime;
fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
switch (animation)
{
using (fruit.BeginAbsoluteSequence(actionTime))
action(fruit);
case DroppedObjectAnimation.Drop:
d.MoveToY(d.Y + 75, 750, Easing.InSine);
d.FadeOut(750);
break;
fruit.Expire();
case DroppedObjectAnimation.Explode:
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
d.MoveToX(d.X + originalX * 6, 1000);
d.FadeOut(750);
break;
}
d.Expire();
}
}
public enum DroppedObjectAnimation
{
Drop,
Explode
}
}

View File

@ -22,11 +22,6 @@ namespace osu.Game.Rulesets.Catch.UI
public readonly Catcher MovableCatcher;
private readonly CatchComboDisplay comboDisplay;
public Container ExplodingFruitTarget
{
set => MovableCatcher.ExplodingFruitTarget = value;
}
public CatcherArea(BeatmapDifficulty difficulty = null)
{
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
@ -41,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI
Margin = new MarginPadding { Bottom = 350f },
X = CatchPlayfield.CENTER_X
},
MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
MovableCatcher = new Catcher(this, this, difficulty) { X = CatchPlayfield.CENTER_X },
};
}