mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 17:32:54 +08:00
Merge pull request #11071 from ekrctb/caught-object-refactor
This commit is contained in:
commit
ff64ba1b08
@ -1,26 +1,194 @@
|
|||||||
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatcher : CatchSkinnableTestScene
|
public class TestSceneCatcher : OsuTestScene
|
||||||
{
|
{
|
||||||
[BackgroundDependencyLoader]
|
[Resolved]
|
||||||
private void load()
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
|
private Container droppedObjectContainer;
|
||||||
|
|
||||||
|
private TestCatcher catcher;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
SetContents(() => new Catcher(new Container())
|
var difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
var trailContainer = new Container();
|
||||||
|
droppedObjectContainer = new Container();
|
||||||
|
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
|
||||||
|
|
||||||
|
Child = new Container
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.None,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
trailContainer,
|
||||||
|
droppedObjectContainer,
|
||||||
|
catcher
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatcherCatchWidth()
|
||||||
|
{
|
||||||
|
var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
|
||||||
|
AddStep("catch fruit", () =>
|
||||||
|
{
|
||||||
|
attemptCatch(new Fruit { X = -halfWidth + 1 });
|
||||||
|
attemptCatch(new Fruit { X = halfWidth - 1 });
|
||||||
});
|
});
|
||||||
|
checkPlate(2);
|
||||||
|
AddStep("miss fruit", () =>
|
||||||
|
{
|
||||||
|
attemptCatch(new Fruit { X = -halfWidth - 1 });
|
||||||
|
attemptCatch(new Fruit { X = halfWidth + 1 });
|
||||||
|
});
|
||||||
|
checkPlate(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitChangesCatcherState()
|
||||||
|
{
|
||||||
|
AddStep("miss fruit", () => attemptCatch(new Fruit { X = 100 }));
|
||||||
|
checkState(CatcherAnimationState.Fail);
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
checkState(CatcherAnimationState.Idle);
|
||||||
|
AddStep("catch kiai fruit", () => attemptCatch(new TestKiaiFruit()));
|
||||||
|
checkState(CatcherAnimationState.Kiai);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNormalFruitResetsHyperDashState()
|
||||||
|
{
|
||||||
|
AddStep("catch hyper fruit", () => attemptCatch(new Fruit
|
||||||
|
{
|
||||||
|
HyperDashTarget = new Fruit { X = 100 }
|
||||||
|
}));
|
||||||
|
checkHyperDash(true);
|
||||||
|
AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
|
||||||
|
checkHyperDash(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTinyDropletMissPreservesCatcherState()
|
||||||
|
{
|
||||||
|
AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
|
||||||
|
{
|
||||||
|
HyperDashTarget = new Fruit { X = 100 }
|
||||||
|
}));
|
||||||
|
AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
|
||||||
|
AddStep("miss tiny droplet", () => attemptCatch(new TinyDroplet { X = 100 }));
|
||||||
|
// catcher state and hyper dash state is preserved
|
||||||
|
checkState(CatcherAnimationState.Kiai);
|
||||||
|
checkHyperDash(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBananaMissPreservesCatcherState()
|
||||||
|
{
|
||||||
|
AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
|
||||||
|
{
|
||||||
|
HyperDashTarget = new Fruit { X = 100 }
|
||||||
|
}));
|
||||||
|
AddStep("miss banana", () => attemptCatch(new Banana { X = 100 }));
|
||||||
|
// catcher state is preserved but hyper dash state is reset
|
||||||
|
checkState(CatcherAnimationState.Kiai);
|
||||||
|
checkHyperDash(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatcherStacking()
|
||||||
|
{
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
checkPlate(1);
|
||||||
|
AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
|
||||||
|
checkPlate(10);
|
||||||
|
AddAssert("caught objects are stacked", () =>
|
||||||
|
catcher.CaughtObjects.All(obj => obj.Y <= 0) &&
|
||||||
|
catcher.CaughtObjects.Any(obj => obj.Y == 0) &&
|
||||||
|
catcher.CaughtObjects.Any(obj => obj.Y < -20));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCatcherExplosionAndDropping()
|
||||||
|
{
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
|
||||||
|
AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1);
|
||||||
|
AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
|
||||||
|
AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
|
||||||
|
AddStep("explode", () => catcher.Explode());
|
||||||
|
AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
||||||
|
AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
|
||||||
|
AddStep("catch fruits", () => attemptCatch(new Fruit(), 10));
|
||||||
|
AddStep("drop", () => catcher.Drop());
|
||||||
|
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestHitLighting(bool enabled)
|
||||||
|
{
|
||||||
|
AddStep($"{(enabled ? "enable" : "disable")} hit lighting", () => config.Set(OsuSetting.HitLighting, enabled));
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
AddAssert("check hit lighting", () => catcher.ChildrenOfType<HitExplosion>().Any() == enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
||||||
|
|
||||||
|
private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
|
||||||
|
|
||||||
|
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
|
||||||
|
|
||||||
|
private void attemptCatch(CatchHitObject hitObject, int count = 1)
|
||||||
|
{
|
||||||
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
catcher.AttemptCatch(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestCatcher : Catcher
|
||||||
|
{
|
||||||
|
public IEnumerable<DrawablePalpableCatchHitObject> CaughtObjects => this.ChildrenOfType<DrawablePalpableCatchHitObject>();
|
||||||
|
|
||||||
|
public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
|
||||||
|
: base(trailsTarget, droppedObjectTarget, difficulty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestKiaiFruit : Fruit
|
||||||
|
{
|
||||||
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||||
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
@ -27,81 +28,68 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
private Catcher catcher => this.ChildrenOfType<CatcherArea>().First().MovableCatcher;
|
private Catcher catcher => this.ChildrenOfType<Catcher>().First();
|
||||||
|
|
||||||
|
private float circleSize;
|
||||||
|
|
||||||
public TestSceneCatcherArea()
|
public TestSceneCatcherArea()
|
||||||
{
|
{
|
||||||
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
||||||
AddToggleStep("Hyperdash", t =>
|
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
||||||
CreatedDrawables.OfType<CatchInputManager>().Select(i => i.Child)
|
|
||||||
.OfType<TestCatcherArea>().ForEach(c => c.ToggleHyperDash(t)));
|
|
||||||
|
|
||||||
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
{
|
AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true }));
|
||||||
X = catcher.X
|
AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit()));
|
||||||
}), 20);
|
AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true }));
|
||||||
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
|
||||||
{
|
|
||||||
X = catcher.X,
|
|
||||||
LastInCombo = true,
|
|
||||||
}), 20);
|
|
||||||
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
|
|
||||||
{
|
|
||||||
X = catcher.X
|
|
||||||
}), 20);
|
|
||||||
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
|
|
||||||
{
|
|
||||||
X = catcher.X + 100,
|
|
||||||
LastInCombo = true,
|
|
||||||
}, true), 20);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
private void attemptCatch(Fruit fruit)
|
||||||
[TestCase(false)]
|
|
||||||
public void TestHitLighting(bool enable)
|
|
||||||
{
|
{
|
||||||
AddStep("create catcher", () => createCatcher(5));
|
fruit.X += catcher.X;
|
||||||
|
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
|
||||||
AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable));
|
|
||||||
AddStep("catch fruit", () => catchFruit(new TestFruit(false)
|
|
||||||
{
|
{
|
||||||
X = catcher.X
|
CircleSize = circleSize
|
||||||
}));
|
});
|
||||||
AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
|
||||||
{
|
|
||||||
X = catcher.X,
|
|
||||||
LastInCombo = true
|
|
||||||
}));
|
|
||||||
AddAssert("check hit explosion", () => catcher.ChildrenOfType<HitExplosion>().Any() == enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void catchFruit(Fruit fruit, bool miss = false)
|
foreach (var area in this.ChildrenOfType<CatcherArea>())
|
||||||
{
|
|
||||||
this.ChildrenOfType<CatcherArea>().ForEach(area =>
|
|
||||||
{
|
{
|
||||||
DrawableFruit drawable = new DrawableFruit(fruit);
|
DrawableFruit drawable = new DrawableFruit(fruit);
|
||||||
area.Add(drawable);
|
area.Add(drawable);
|
||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
area.AttemptCatch(fruit);
|
bool caught = area.AttemptCatch(fruit);
|
||||||
area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
|
area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement())
|
||||||
|
{
|
||||||
|
Type = caught ? HitResult.Great : HitResult.Miss
|
||||||
|
});
|
||||||
|
|
||||||
drawable.Expire();
|
drawable.Expire();
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createCatcher(float size)
|
private void createCatcher(float size)
|
||||||
{
|
{
|
||||||
SetContents(() => new CatchInputManager(catchRuleset)
|
circleSize = size;
|
||||||
|
|
||||||
|
SetContents(() =>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
var droppedObjectContainer = new Container();
|
||||||
Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
|
|
||||||
|
return new CatchInputManager(catchRuleset)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.TopCentre,
|
Children = new Drawable[]
|
||||||
},
|
{
|
||||||
|
droppedObjectContainer,
|
||||||
|
new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size })
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,26 +99,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
catchRuleset = rulesets.GetRuleset(2);
|
catchRuleset = rulesets.GetRuleset(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestFruit : Fruit
|
|
||||||
{
|
|
||||||
public TestFruit(bool kiai)
|
|
||||||
{
|
|
||||||
var kiaiCpi = new ControlPointInfo();
|
|
||||||
kiaiCpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
|
||||||
|
|
||||||
ApplyDefaultsToSelf(kiaiCpi, new BeatmapDifficulty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestCatcherArea : CatcherArea
|
private class TestCatcherArea : CatcherArea
|
||||||
{
|
{
|
||||||
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
|
public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
|
||||||
: base(beatmapDifficulty)
|
: base(droppedObjectContainer, beatmapDifficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public new Catcher MovableCatcher => base.MovableCatcher;
|
|
||||||
|
|
||||||
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
|
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddStep("create hyper-dashing catcher", () =>
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
{
|
{
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
|
||||||
|
|
||||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
public int RandomSeed => HitObject?.RandomSeed ?? 0;
|
public int RandomSeed => HitObject?.RandomSeed ?? 0;
|
||||||
|
@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool StaysOnPlate => true;
|
public virtual bool StaysOnPlate => true;
|
||||||
|
|
||||||
|
public float DisplayRadius => CatchHitObject.OBJECT_RADIUS * HitObject.Scale * ScaleFactor;
|
||||||
|
|
||||||
protected readonly Container ScaleContainer;
|
protected readonly Container ScaleContainer;
|
||||||
|
|
||||||
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
|
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
|
||||||
|
@ -36,21 +36,20 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
||||||
{
|
{
|
||||||
var explodingFruitContainer = new Container
|
var droppedObjectContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
|
|
||||||
CatcherArea = new CatcherArea(difficulty)
|
CatcherArea = new CatcherArea(droppedObjectContainer, difficulty)
|
||||||
{
|
{
|
||||||
ExplodingFruitTarget = explodingFruitContainer,
|
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
};
|
};
|
||||||
|
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
explodingFruitContainer,
|
droppedObjectContainer,
|
||||||
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
||||||
HitObjectContainer,
|
HitObjectContainer,
|
||||||
CatcherArea,
|
CatcherArea,
|
||||||
|
@ -17,7 +17,6 @@ using osu.Game.Configuration;
|
|||||||
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;
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -47,19 +46,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const double BASE_SPEED = 1.0;
|
public const double BASE_SPEED = 1.0;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget;
|
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruitContainer { get; } = new Container<DrawableHitObject>
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
};
|
|
||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private readonly Container trailsTarget;
|
private readonly Container trailsTarget;
|
||||||
|
|
||||||
private CatcherTrailDisplay trails;
|
private CatcherTrailDisplay trails;
|
||||||
|
|
||||||
|
private readonly Container droppedObjectTarget;
|
||||||
|
|
||||||
|
private readonly Container<DrawablePalpableCatchHitObject> caughtFruitContainer;
|
||||||
|
|
||||||
public CatcherAnimationState CurrentState { get; private set; }
|
public CatcherAnimationState CurrentState { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -92,9 +87,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
private readonly float catchWidth;
|
||||||
|
|
||||||
private CatcherSprite catcherIdle;
|
private readonly CatcherSprite catcherIdle;
|
||||||
private CatcherSprite catcherKiai;
|
private readonly CatcherSprite catcherKiai;
|
||||||
private CatcherSprite catcherFail;
|
private readonly CatcherSprite catcherFail;
|
||||||
|
|
||||||
private CatcherSprite currentCatcher;
|
private CatcherSprite currentCatcher;
|
||||||
|
|
||||||
@ -108,12 +103,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private float hyperDashTargetPosition;
|
private float hyperDashTargetPosition;
|
||||||
private Bindable<bool> hitLighting;
|
private Bindable<bool> hitLighting;
|
||||||
|
|
||||||
private DrawablePool<HitExplosion> hitExplosionPool;
|
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
||||||
private Container<HitExplosion> hitExplosionContainer;
|
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.trailsTarget = trailsTarget;
|
||||||
|
this.droppedObjectTarget = droppedObjectTarget;
|
||||||
|
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
@ -122,17 +118,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
catchWidth = CalculateCatchWidth(Scale);
|
catchWidth = CalculateCatchWidth(Scale);
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuConfigManager config)
|
|
||||||
{
|
|
||||||
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
||||||
caughtFruitContainer,
|
caughtFruitContainer = new Container<DrawablePalpableCatchHitObject>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
},
|
||||||
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -154,7 +148,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||||
trails = new CatcherTrailDisplay(this);
|
trails = new CatcherTrailDisplay(this);
|
||||||
|
|
||||||
updateCatcher();
|
updateCatcher();
|
||||||
@ -176,56 +175,19 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Vector2 calculateScale(BeatmapDifficulty difficulty)
|
private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
||||||
=> new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the width of the area used for attempting catches in gameplay.
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scale">The scale of the catcher.</param>
|
/// <param name="scale">The scale of the catcher.</param>
|
||||||
internal static float CalculateCatchWidth(Vector2 scale)
|
internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
|
||||||
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the width of the area used for attempting catches in gameplay.
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="difficulty">The beatmap difficulty.</param>
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
|
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty));
|
||||||
=> CalculateCatchWidth(calculateScale(difficulty));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Add a caught fruit to the catcher's stack.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fruit">The fruit that was caught.</param>
|
|
||||||
public void PlaceOnPlate(DrawableCatchHitObject fruit)
|
|
||||||
{
|
|
||||||
var ourRadius = fruit.DisplayRadius;
|
|
||||||
float theirRadius = 0;
|
|
||||||
|
|
||||||
const float allowance = 10;
|
|
||||||
|
|
||||||
while (caughtFruitContainer.Any(f =>
|
|
||||||
f.LifetimeEnd == double.MaxValue &&
|
|
||||||
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
|
|
||||||
{
|
|
||||||
var diff = (ourRadius + theirRadius) / allowance;
|
|
||||||
fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
|
||||||
fruit.Y -= RNG.NextSingle() * diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
|
||||||
|
|
||||||
caughtFruitContainer.Add(fruit);
|
|
||||||
|
|
||||||
if (hitLighting.Value)
|
|
||||||
{
|
|
||||||
HitExplosion hitExplosion = hitExplosionPool.Get();
|
|
||||||
hitExplosion.X = fruit.X;
|
|
||||||
hitExplosion.Scale = new Vector2(fruit.HitObject.Scale);
|
|
||||||
hitExplosion.ObjectColour = fruit.AccentColour.Value;
|
|
||||||
hitExplosionContainer.Add(hitExplosion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Let the catcher attempt to catch a fruit.
|
/// Let the catcher attempt to catch a fruit.
|
||||||
@ -247,7 +209,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||||
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
catchObjectPosition <= catcherPosition + halfCatchWidth;
|
||||||
|
|
||||||
// only update hyperdash state if we are not catching a tiny droplet.
|
if (validCatch)
|
||||||
|
placeCaughtObject(fruit);
|
||||||
|
|
||||||
|
// droplet doesn't affect the catcher state
|
||||||
if (fruit is TinyDroplet) return validCatch;
|
if (fruit is TinyDroplet) return validCatch;
|
||||||
|
|
||||||
if (validCatch && fruit.HyperDash)
|
if (validCatch && fruit.HyperDash)
|
||||||
@ -301,24 +266,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runHyperDashStateTransition(bool hyperDashing)
|
public void UpdatePosition(float position)
|
||||||
{
|
{
|
||||||
updateTrailVisibility();
|
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
if (hyperDashing)
|
if (position == X)
|
||||||
{
|
return;
|
||||||
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
||||||
}
|
X = position;
|
||||||
else
|
|
||||||
{
|
|
||||||
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
|
|
||||||
|
|
||||||
public bool OnPressed(CatchAction action)
|
public bool OnPressed(CatchAction action)
|
||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
@ -357,56 +315,34 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatePosition(float position)
|
|
||||||
{
|
|
||||||
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
|
||||||
|
|
||||||
if (position == X)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
|
||||||
X = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drop any fruit off the plate.
|
/// Drop any fruit off the plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Drop()
|
public void Drop() => clearPlate(DroppedObjectAnimation.Drop);
|
||||||
{
|
|
||||||
foreach (var f in caughtFruitContainer.ToArray())
|
|
||||||
Drop(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Explode any fruit off the plate.
|
/// Explode all fruit off the plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Explode()
|
public void Explode() => clearPlate(DroppedObjectAnimation.Explode);
|
||||||
{
|
|
||||||
foreach (var f in caughtFruitContainer.ToArray())
|
|
||||||
Explode(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Drop(DrawableHitObject fruit)
|
private void runHyperDashStateTransition(bool hyperDashing)
|
||||||
{
|
{
|
||||||
removeFromPlateWithTransform(fruit, f =>
|
updateTrailVisibility();
|
||||||
|
|
||||||
|
if (hyperDashing)
|
||||||
{
|
{
|
||||||
f.MoveToY(f.Y + 75, 750, Easing.InSine);
|
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
f.FadeOut(750);
|
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
});
|
}
|
||||||
}
|
else
|
||||||
|
|
||||||
public void Explode(DrawableHitObject 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);
|
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
f.MoveToX(f.X + originalX * 6, 1000);
|
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
f.FadeOut(750);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
|
||||||
|
|
||||||
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
{
|
{
|
||||||
base.SkinChanged(skin, allowFallback);
|
base.SkinChanged(skin, allowFallback);
|
||||||
@ -479,33 +415,143 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action)
|
private void placeCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
if (ExplodingFruitTarget != null)
|
var caughtObject = createCaughtObject(source);
|
||||||
|
|
||||||
|
if (caughtObject == null) return;
|
||||||
|
|
||||||
|
caughtObject.RelativePositionAxes = Axes.None;
|
||||||
|
caughtObject.X = source.X - X;
|
||||||
|
caughtObject.IsOnPlate = true;
|
||||||
|
|
||||||
|
caughtObject.Anchor = Anchor.TopCentre;
|
||||||
|
caughtObject.Origin = Anchor.Centre;
|
||||||
|
caughtObject.Scale *= 0.5f;
|
||||||
|
caughtObject.LifetimeStart = source.StartTime;
|
||||||
|
caughtObject.LifetimeEnd = double.MaxValue;
|
||||||
|
|
||||||
|
adjustPositionInStack(caughtObject);
|
||||||
|
|
||||||
|
caughtFruitContainer.Add(caughtObject);
|
||||||
|
|
||||||
|
addLighting(caughtObject);
|
||||||
|
|
||||||
|
if (!caughtObject.StaysOnPlate)
|
||||||
|
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void adjustPositionInStack(DrawablePalpableCatchHitObject caughtObject)
|
||||||
|
{
|
||||||
|
const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
|
||||||
|
const float allowance = 10;
|
||||||
|
|
||||||
|
float caughtObjectRadius = caughtObject.DisplayRadius;
|
||||||
|
|
||||||
|
while (caughtFruitContainer.Any(f => Vector2Extensions.Distance(f.Position, caughtObject.Position) < (caughtObjectRadius + radius_div_2) / (allowance / 2)))
|
||||||
{
|
{
|
||||||
fruit.Anchor = Anchor.TopLeft;
|
float diff = (caughtObjectRadius + radius_div_2) / allowance;
|
||||||
fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
|
||||||
|
|
||||||
if (!caughtFruitContainer.Remove(fruit))
|
caughtObject.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
||||||
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
|
caughtObject.Y -= RNG.NextSingle() * diff;
|
||||||
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
|
|
||||||
return;
|
|
||||||
|
|
||||||
ExplodingFruitTarget.Add(fruit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var actionTime = Clock.CurrentTime;
|
caughtObject.X = Math.Clamp(caughtObject.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
||||||
|
}
|
||||||
|
|
||||||
fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
|
private void addLighting(DrawablePalpableCatchHitObject caughtObject)
|
||||||
onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
|
{
|
||||||
|
if (!hitLighting.Value) return;
|
||||||
|
|
||||||
void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
|
HitExplosion hitExplosion = hitExplosionPool.Get();
|
||||||
|
hitExplosion.X = caughtObject.X;
|
||||||
|
hitExplosion.Scale = new Vector2(caughtObject.HitObject.Scale);
|
||||||
|
hitExplosion.ObjectColour = caughtObject.AccentColour.Value;
|
||||||
|
hitExplosionContainer.Add(hitExplosion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source)
|
||||||
|
{
|
||||||
|
switch (source)
|
||||||
{
|
{
|
||||||
using (fruit.BeginAbsoluteSequence(actionTime))
|
case Banana banana:
|
||||||
action(fruit);
|
return new DrawableBanana(banana);
|
||||||
|
|
||||||
fruit.Expire();
|
case Fruit fruit:
|
||||||
|
return new DrawableFruit(fruit);
|
||||||
|
|
||||||
|
case TinyDroplet tiny:
|
||||||
|
return new DrawableTinyDroplet(tiny);
|
||||||
|
|
||||||
|
case Droplet droplet:
|
||||||
|
return new DrawableDroplet(droplet);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
|
{
|
||||||
|
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(DrawablePalpableCatchHitObject d, DroppedObjectAnimation animation)
|
||||||
|
{
|
||||||
|
var originalX = d.X * Scale.X;
|
||||||
|
var startTime = Clock.CurrentTime;
|
||||||
|
|
||||||
|
d.Anchor = Anchor.TopLeft;
|
||||||
|
d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget);
|
||||||
|
|
||||||
|
// we cannot just apply the transforms because DHO clears transforms when state is updated
|
||||||
|
d.ApplyCustomUpdateState += (o, state) => animate(o, animation, originalX, startTime);
|
||||||
|
if (d.IsLoaded)
|
||||||
|
animate(d, animation, originalX, startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animate(Drawable d, DroppedObjectAnimation animation, float originalX, double startTime)
|
||||||
|
{
|
||||||
|
using (d.BeginAbsoluteSequence(startTime))
|
||||||
|
{
|
||||||
|
switch (animation)
|
||||||
|
{
|
||||||
|
case DroppedObjectAnimation.Drop:
|
||||||
|
d.MoveToY(d.Y + 75, 750, Easing.InSine);
|
||||||
|
d.FadeOut(750);
|
||||||
|
break;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum DroppedObjectAnimation
|
||||||
|
{
|
||||||
|
Drop,
|
||||||
|
Explode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// 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 osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -23,14 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public readonly Catcher MovableCatcher;
|
public readonly Catcher MovableCatcher;
|
||||||
private readonly CatchComboDisplay comboDisplay;
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget
|
public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
||||||
{
|
|
||||||
set => MovableCatcher.ExplodingFruitTarget = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DrawableCatchHitObject lastPlateableFruit;
|
|
||||||
|
|
||||||
public CatcherArea(BeatmapDifficulty difficulty = null)
|
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -44,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Margin = new MarginPadding { Bottom = 350f },
|
Margin = new MarginPadding { Bottom = 350f },
|
||||||
X = CatchPlayfield.CENTER_X
|
X = CatchPlayfield.CENTER_X
|
||||||
},
|
},
|
||||||
MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
|
MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,47 +45,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (!result.Type.IsScorable())
|
if (!result.Type.IsScorable())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
void runAfterLoaded(Action action)
|
|
||||||
{
|
|
||||||
if (lastPlateableFruit == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// this is required to make this run after the last caught fruit runs updateState() at least once.
|
|
||||||
// TODO: find a better alternative
|
|
||||||
if (lastPlateableFruit.IsLoaded)
|
|
||||||
action();
|
|
||||||
else
|
|
||||||
lastPlateableFruit.OnLoadComplete += _ => action();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit)
|
|
||||||
{
|
|
||||||
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
|
|
||||||
var caughtFruit = createCaughtFruit(fruit);
|
|
||||||
|
|
||||||
if (caughtFruit == null) return;
|
|
||||||
|
|
||||||
caughtFruit.RelativePositionAxes = Axes.None;
|
|
||||||
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(hitObject.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
|
|
||||||
caughtFruit.IsOnPlate = true;
|
|
||||||
|
|
||||||
caughtFruit.Anchor = Anchor.TopCentre;
|
|
||||||
caughtFruit.Origin = Anchor.Centre;
|
|
||||||
caughtFruit.Scale *= 0.5f;
|
|
||||||
caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime;
|
|
||||||
caughtFruit.LifetimeEnd = double.MaxValue;
|
|
||||||
|
|
||||||
MovableCatcher.PlaceOnPlate(caughtFruit);
|
|
||||||
lastPlateableFruit = caughtFruit;
|
|
||||||
|
|
||||||
if (!fruit.StaysOnPlate)
|
|
||||||
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hitObject.HitObject.LastInCombo)
|
if (hitObject.HitObject.LastInCombo)
|
||||||
{
|
{
|
||||||
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
|
||||||
runAfterLoaded(() => MovableCatcher.Explode());
|
MovableCatcher.Explode();
|
||||||
else
|
else
|
||||||
MovableCatcher.Drop();
|
MovableCatcher.Drop();
|
||||||
}
|
}
|
||||||
@ -104,10 +59,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
=> comboDisplay.OnRevertResult(fruit, result);
|
=> comboDisplay.OnRevertResult(fruit, result);
|
||||||
|
|
||||||
public void OnReleased(CatchAction action)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AttemptCatch(CatchHitObject obj)
|
public bool AttemptCatch(CatchHitObject obj)
|
||||||
{
|
{
|
||||||
return MovableCatcher.AttemptCatch(obj);
|
return MovableCatcher.AttemptCatch(obj);
|
||||||
@ -124,26 +75,5 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
comboDisplay.X = MovableCatcher.X;
|
comboDisplay.X = MovableCatcher.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject)
|
|
||||||
{
|
|
||||||
switch (hitObject.HitObject)
|
|
||||||
{
|
|
||||||
case Banana banana:
|
|
||||||
return new DrawableBanana(banana);
|
|
||||||
|
|
||||||
case Fruit fruit:
|
|
||||||
return new DrawableFruit(fruit);
|
|
||||||
|
|
||||||
case TinyDroplet tiny:
|
|
||||||
return new DrawableTinyDroplet(tiny);
|
|
||||||
|
|
||||||
case Droplet droplet:
|
|
||||||
return new DrawableDroplet(droplet);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user