diff --git a/osu.Android.props b/osu.Android.props
index 9a3d42d6b7..eaedcb7bc3 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index cf6011d721..e8bb57cdf3 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -18,6 +18,7 @@ using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
@@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved]
private OsuConfigManager config { get; set; }
- private Container droppedObjectContainer;
+ private Container droppedObjectContainer;
private TestCatcher catcher;
@@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Tests
};
var trailContainer = new Container();
- droppedObjectContainer = new Container();
+ droppedObjectContainer = new Container();
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
Child = new Container
@@ -65,13 +66,11 @@ namespace osu.Game.Rulesets.Catch.Tests
JudgementResult result2 = null;
AddStep("catch hyper fruit", () =>
{
- drawableObject1 = createDrawableObject(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
- result1 = attemptCatch(drawableObject1);
+ attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
});
AddStep("catch normal fruit", () =>
{
- drawableObject2 = createDrawableObject(new Fruit());
- result2 = attemptCatch(drawableObject2);
+ attemptCatch(new Fruit(), out drawableObject2, out result2);
});
AddStep("revert second result", () =>
{
@@ -92,8 +91,7 @@ namespace osu.Game.Rulesets.Catch.Tests
JudgementResult result = null;
AddStep("catch kiai fruit", () =>
{
- drawableObject = createDrawableObject(new TestKiaiFruit());
- result = attemptCatch(drawableObject);
+ attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
});
checkState(CatcherAnimationState.Kiai);
AddStep("revert result", () =>
@@ -200,13 +198,22 @@ namespace osu.Game.Rulesets.Catch.Tests
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
}
- [TestCase(true)]
- [TestCase(false)]
- public void TestHitLighting(bool enabled)
+ [Test]
+ public void TestHitLightingColour()
{
- AddStep($"{(enabled ? "enable" : "disable")} hit lighting", () => config.Set(OsuSetting.HitLighting, enabled));
+ var fruitColour = SkinConfiguration.DefaultComboColours[1];
+ AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true));
AddStep("catch fruit", () => attemptCatch(new Fruit()));
- AddAssert("check hit lighting", () => catcher.ChildrenOfType().Any() == enabled);
+ AddAssert("correct hit lighting colour", () =>
+ catcher.ChildrenOfType().First()?.ObjectColour == fruitColour);
+ }
+
+ [Test]
+ public void TestHitLightingDisabled()
+ {
+ AddStep("disable hit lighting", () => config.Set(OsuSetting.HitLighting, false));
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddAssert("no hit lighting", () => !catcher.ChildrenOfType().Any());
}
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
@@ -218,18 +225,34 @@ namespace osu.Game.Rulesets.Catch.Tests
private void attemptCatch(CatchHitObject hitObject, int count = 1)
{
for (var i = 0; i < count; i++)
- attemptCatch(createDrawableObject(hitObject));
+ attemptCatch(hitObject, out _, out _);
}
- private JudgementResult attemptCatch(DrawableCatchHitObject drawableObject)
+ private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
{
- drawableObject.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- var result = new CatchJudgementResult(drawableObject.HitObject, drawableObject.HitObject.CreateJudgement())
+ hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ drawableObject = createDrawableObject(hitObject);
+ result = createResult(hitObject);
+ applyResult(drawableObject, result);
+ }
+
+ private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
+ {
+ // Load DHO to set colour of hit explosion correctly
+ Add(drawableObject);
+ drawableObject.OnLoadComplete += _ =>
{
- Type = catcher.CanCatch(drawableObject.HitObject) ? HitResult.Great : HitResult.Miss
+ catcher.OnNewResult(drawableObject, result);
+ drawableObject.Expire();
+ };
+ }
+
+ private JudgementResult createResult(CatchHitObject hitObject)
+ {
+ return new CatchJudgementResult(hitObject, hitObject.CreateJudgement())
+ {
+ Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss
};
- catcher.OnNewResult(drawableObject, result);
- return result;
}
private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
@@ -252,9 +275,9 @@ namespace osu.Game.Rulesets.Catch.Tests
public class TestCatcher : Catcher
{
- public IEnumerable CaughtObjects => this.ChildrenOfType();
+ public IEnumerable CaughtObjects => this.ChildrenOfType();
- public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
+ public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
: base(trailsTarget, droppedObjectTarget, difficulty)
{
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index 8602c7aad1..31c285ef22 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -73,7 +73,10 @@ namespace osu.Game.Rulesets.Catch.Tests
SetContents(() =>
{
- var droppedObjectContainer = new Container();
+ var droppedObjectContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ };
return new CatchInputManager(catchRuleset)
{
@@ -99,7 +102,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private class TestCatcherArea : CatcherArea
{
- public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
+ public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
: base(droppedObjectContainer, beatmapDifficulty)
{
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
index 2ffebb7de1..c888dc0a65 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
@@ -5,19 +5,20 @@ using NUnit.Framework;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Tests.Visual;
+using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneFruitRandomness : OsuTestScene
{
- private readonly TestDrawableFruit drawableFruit;
- private readonly TestDrawableBanana drawableBanana;
+ private readonly DrawableFruit drawableFruit;
+ private readonly DrawableBanana drawableBanana;
public TestSceneFruitRandomness()
{
- drawableFruit = new TestDrawableFruit(new Fruit());
- drawableBanana = new TestDrawableBanana(new Banana());
+ drawableFruit = new DrawableFruit(new Fruit());
+ drawableBanana = new DrawableBanana(new Banana());
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
@@ -37,16 +38,16 @@ namespace osu.Game.Rulesets.Catch.Tests
float fruitRotation = 0;
float bananaRotation = 0;
- float bananaScale = 0;
+ Vector2 bananaSize = new Vector2();
Color4 bananaColour = new Color4();
AddStep("Initialize start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
- fruitRotation = drawableFruit.InnerRotation;
- bananaRotation = drawableBanana.InnerRotation;
- bananaScale = drawableBanana.InnerScale;
+ fruitRotation = drawableFruit.DisplayRotation;
+ bananaRotation = drawableBanana.DisplayRotation;
+ bananaSize = drawableBanana.DisplaySize;
bananaColour = drawableBanana.AccentColour.Value;
});
@@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Tests
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
});
- AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation);
- AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation);
- AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale);
+ AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
+ AddAssert("banana rotation is changed", () => drawableBanana.DisplayRotation != bananaRotation);
+ AddAssert("banana size is changed", () => drawableBanana.DisplaySize != bananaSize);
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
AddStep("reset start time", () =>
@@ -65,32 +66,11 @@ namespace osu.Game.Rulesets.Catch.Tests
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
});
- AddAssert("rotation and scale restored", () =>
- drawableFruit.InnerRotation == fruitRotation &&
- drawableBanana.InnerRotation == bananaRotation &&
- drawableBanana.InnerScale == bananaScale &&
+ AddAssert("rotation and size restored", () =>
+ drawableFruit.DisplayRotation == fruitRotation &&
+ drawableBanana.DisplayRotation == bananaRotation &&
+ drawableBanana.DisplaySize == bananaSize &&
drawableBanana.AccentColour.Value == bananaColour);
}
-
- private class TestDrawableFruit : DrawableFruit
- {
- public float InnerRotation => ScaleContainer.Rotation;
-
- public TestDrawableFruit(Fruit h)
- : base(h)
- {
- }
- }
-
- private class TestDrawableBanana : DrawableBanana
- {
- public float InnerRotation => ScaleContainer.Rotation;
- public float InnerScale => ScaleContainer.Scale.X;
-
- public TestDrawableBanana(Banana h)
- : base(h)
- {
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
index d78dc2d2b5..683a776dcc 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
@@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("create hyper-dashing catcher", () =>
{
- Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
new file mode 100644
index 0000000000..8a91f82437
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Catch.Skinning.Default;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ ///
+ /// Represents a caught by the catcher.
+ ///
+ public class CaughtBanana : CaughtObject
+ {
+ public CaughtBanana()
+ : base(CatchSkinComponents.Banana, _ => new BananaPiece())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
new file mode 100644
index 0000000000..4a3397feff
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Catch.Skinning.Default;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ ///
+ /// Represents a caught by the catcher.
+ ///
+ public class CaughtDroplet : CaughtObject
+ {
+ public override bool StaysOnPlate => false;
+
+ public CaughtDroplet()
+ : base(CatchSkinComponents.Droplet, _ => new DropletPiece())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
new file mode 100644
index 0000000000..140b411c88
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
@@ -0,0 +1,29 @@
+// 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.Bindables;
+using osu.Game.Rulesets.Catch.Skinning.Default;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ ///
+ /// Represents a caught by the catcher.
+ ///
+ public class CaughtFruit : CaughtObject, IHasFruitState
+ {
+ public Bindable VisualRepresentation { get; } = new Bindable();
+
+ public CaughtFruit()
+ : base(CatchSkinComponents.Fruit, _ => new FruitPiece())
+ {
+ }
+
+ public override void CopyStateFrom(IHasCatchObjectState objectState)
+ {
+ base.CopyStateFrom(objectState);
+
+ var fruitState = (IHasFruitState)objectState;
+ VisualRepresentation.Value = fruitState.VisualRepresentation.Value;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
new file mode 100644
index 0000000000..524505d588
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
@@ -0,0 +1,64 @@
+// 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.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ ///
+ /// Represents a caught by the catcher.
+ ///
+ [Cached(typeof(IHasCatchObjectState))]
+ public abstract class CaughtObject : SkinnableDrawable, IHasCatchObjectState
+ {
+ public PalpableCatchHitObject HitObject { get; private set; }
+ public Bindable AccentColour { get; } = new Bindable();
+ public Bindable HyperDash { get; } = new Bindable();
+
+ public Vector2 DisplaySize => Size * Scale;
+
+ public float DisplayRotation => Rotation;
+
+ ///
+ /// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
+ ///
+ public virtual bool StaysOnPlate => true;
+
+ public override bool RemoveWhenNotAlive => true;
+
+ protected CaughtObject(CatchSkinComponents skinComponent, Func defaultImplementation)
+ : base(new CatchSkinComponent(skinComponent), defaultImplementation)
+ {
+ Origin = Anchor.Centre;
+
+ RelativeSizeAxes = Axes.None;
+ Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
+ }
+
+ ///
+ /// Copies the hit object visual state from another object.
+ ///
+ public virtual void CopyStateFrom(IHasCatchObjectState objectState)
+ {
+ HitObject = objectState.HitObject;
+ Scale = Vector2.Divide(objectState.DisplaySize, Size);
+ Rotation = objectState.DisplayRotation;
+ AccentColour.Value = objectState.AccentColour.Value;
+ HyperDash.Value = objectState.HyperDash.Value;
+ }
+
+ protected override void FreeAfterUse()
+ {
+ ClearTransforms();
+ Alpha = 1;
+
+ base.FreeAfterUse();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
index 2a543a0e04..c1b41a7afc 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- ScaleContainer.Child = new SkinnableDrawable(
+ ScalingContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(CatchSkinComponents.Banana),
_ => new BananaPiece());
}
@@ -44,12 +44,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
const float end_scale = 0.6f;
const float random_scale_range = 1.6f;
- ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
- .Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
+ ScalingContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
+ .Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
- ScaleContainer.RotateTo(getRandomAngle(1))
- .Then()
- .RotateTo(getRandomAngle(2), HitObject.TimePreempt);
+ ScalingContainer.RotateTo(getRandomAngle(1))
+ .Then()
+ .RotateTo(getRandomAngle(2), HitObject.TimePreempt);
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
index 70efe9cf29..bfd124c691 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
@@ -50,10 +50,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public Func CheckPosition;
- public bool IsOnPlate;
-
- public override bool RemoveWhenNotAlive => IsOnPlate;
-
protected override JudgementResult CreateResult(Judgement judgement) => new CatchJudgementResult(HitObject, judgement);
protected override void CheckForResult(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
index 81c8de2e59..2dce9507a5 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
@@ -11,8 +11,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableDroplet : DrawablePalpableCatchHitObject
{
- public override bool StaysOnPlate => false;
-
public DrawableDroplet()
: this(null)
{
@@ -26,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- ScaleContainer.Child = new SkinnableDrawable(
+ ScalingContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(CatchSkinComponents.Droplet),
_ => new DropletPiece());
}
@@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
float startRotation = RandomSingle(1) * 20;
double duration = HitObject.TimePreempt + 2000;
- ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
+ ScalingContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
index 0fcd319a93..0b89c46480 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
@@ -10,9 +10,9 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public class DrawableFruit : DrawablePalpableCatchHitObject
+ public class DrawableFruit : DrawablePalpableCatchHitObject, IHasFruitState
{
- public readonly Bindable VisualRepresentation = new Bindable();
+ public Bindable VisualRepresentation { get; } = new Bindable();
public DrawableFruit()
: this(null)
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
}, true);
- ScaleContainer.Child = new SkinnableDrawable(
+ ScalingContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(CatchSkinComponents.Fruit),
_ => new FruitPiece());
}
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
base.UpdateInitialTransforms();
- ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
+ ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
index 0877b5e248..7df06bd92d 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs
@@ -7,32 +7,36 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
- public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject
+ [Cached(typeof(IHasCatchObjectState))]
+ public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject, IHasCatchObjectState
{
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
- public readonly Bindable HyperDash = new Bindable();
+ Bindable IHasCatchObjectState.AccentColour => AccentColour;
- public readonly Bindable ScaleBindable = new Bindable(1);
+ public Bindable HyperDash { get; } = new Bindable();
- public readonly Bindable IndexInBeatmap = new Bindable();
+ public Bindable ScaleBindable { get; } = new Bindable(1);
+
+ public Bindable IndexInBeatmap { get; } = new Bindable();
///
- /// The multiplicative factor applied to scale relative to scale.
+ /// The multiplicative factor applied to relative to scale.
///
protected virtual float ScaleFactor => 1;
///
- /// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
+ /// The container internal transforms (such as scaling based on the circle size) are applied to.
///
- public virtual bool StaysOnPlate => true;
+ protected readonly Container ScalingContainer;
- public float DisplayRadius => CatchHitObject.OBJECT_RADIUS * HitObject.Scale * ScaleFactor;
+ public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
- protected readonly Container ScaleContainer;
+ public float DisplayRotation => ScalingContainer.Rotation;
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
: base(h)
@@ -40,11 +44,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
Origin = Anchor.Centre;
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
- AddInternal(ScaleContainer = new Container
+ AddInternal(ScalingContainer = new Container
{
- RelativeSizeAxes = Axes.Both,
- Origin = Anchor.Centre,
Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2)
});
}
@@ -53,12 +57,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
XBindable.BindValueChanged(x =>
{
- if (!IsOnPlate) X = x.NewValue;
+ X = x.NewValue;
}, true);
ScaleBindable.BindValueChanged(scale =>
{
- ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
+ ScalingContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
+ Size = DisplaySize;
}, true);
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
new file mode 100644
index 0000000000..81b61f0959
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
@@ -0,0 +1,25 @@
+// 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.Bindables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ ///
+ /// Provides a visual state of a .
+ ///
+ public interface IHasCatchObjectState
+ {
+ PalpableCatchHitObject HitObject { get; }
+
+ Bindable AccentColour { get; }
+
+ Bindable HyperDash { get; }
+
+ Vector2 DisplaySize { get; }
+
+ float DisplayRotation { get; }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs
new file mode 100644
index 0000000000..2d4de543c3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs
@@ -0,0 +1,15 @@
+// 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.Bindables;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ ///
+ /// Provides a visual state of a .
+ ///
+ public interface IHasFruitState : IHasCatchObjectState
+ {
+ Bindable VisualRepresentation { get; }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
index d59b6cc0de..51c06c8e37 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/CatchHitObjectPiece.cs
@@ -7,7 +7,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Default
@@ -17,9 +16,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
public readonly Bindable AccentColour = new Bindable();
public readonly Bindable HyperDash = new Bindable();
- [Resolved(canBeNull: true)]
- [CanBeNull]
- protected DrawableHitObject DrawableHitObject { get; private set; }
+ [Resolved]
+ protected IHasCatchObjectState ObjectState { get; private set; }
///
/// A part of this piece that will be faded out while falling in the playfield.
@@ -37,13 +35,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
{
base.LoadComplete();
- var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
-
- if (hitObject != null)
- {
- AccentColour.BindTo(hitObject.AccentColour);
- HyperDash.BindTo(hitObject.HyperDash);
- }
+ AccentColour.BindTo(ObjectState.AccentColour);
+ HyperDash.BindTo(ObjectState.HyperDash);
HyperDash.BindValueChanged(hyper =>
{
@@ -54,8 +47,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
protected override void Update()
{
- if (BorderPiece != null && DrawableHitObject?.HitObject != null)
- BorderPiece.Alpha = (float)Math.Clamp((DrawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1);
+ if (BorderPiece != null)
+ BorderPiece.Alpha = (float)Math.Clamp((ObjectState.HitObject.StartTime - Time.Current) / 500, 0, 1);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
index 2e3803a31a..49f128c960 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/FruitPiece.cs
@@ -39,10 +39,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
{
base.LoadComplete();
- var fruit = (DrawableFruit)DrawableHitObject;
-
- if (fruit != null)
- VisualRepresentation.BindTo(fruit.VisualRepresentation);
+ var fruitState = (IHasFruitState)ObjectState;
+ VisualRepresentation.BindTo(fruitState.VisualRepresentation);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
index 6f93e68594..969cc38e5b 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyFruitPiece.cs
@@ -14,10 +14,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
base.LoadComplete();
- var fruit = (DrawableFruit)DrawableHitObject;
-
- if (fruit != null)
- VisualRepresentation.BindTo(fruit.VisualRepresentation);
+ var fruitState = (IHasFruitState)ObjectState;
+ VisualRepresentation.BindTo(fruitState.VisualRepresentation);
VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs
index 1e68439402..4b1f5a4724 100644
--- a/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyCatchHitObjectPiece.cs
@@ -1,7 +1,6 @@
// 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.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -10,7 +9,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -29,9 +27,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
[Resolved]
protected ISkinSource Skin { get; private set; }
- [Resolved(canBeNull: true)]
- [CanBeNull]
- protected DrawableHitObject DrawableHitObject { get; private set; }
+ [Resolved]
+ protected IHasCatchObjectState ObjectState { get; private set; }
protected LegacyCatchHitObjectPiece()
{
@@ -65,13 +62,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
{
base.LoadComplete();
- var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
-
- if (hitObject != null)
- {
- AccentColour.BindTo(hitObject.AccentColour);
- HyperDash.BindTo(hitObject.HyperDash);
- }
+ AccentColour.BindTo(ObjectState.AccentColour);
+ HyperDash.BindTo(ObjectState.HyperDash);
hyperSprite.Colour = Skin.GetConfig(CatchSkinColour.HyperDashFruit)?.Value ??
Skin.GetConfig(CatchSkinColour.HyperDash)?.Value ??
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index fdc12bf088..73420a9eda 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI
public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation)
{
- var droppedObjectContainer = new Container
+ var droppedObjectContainer = new Container
{
RelativeSizeAxes = Axes.Both,
};
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index a806e623af..f164c2655a 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -53,9 +53,15 @@ namespace osu.Game.Rulesets.Catch.UI
private CatcherTrailDisplay trails;
- private readonly Container droppedObjectTarget;
+ ///
+ /// Contains caught objects on the plate.
+ ///
+ private readonly Container caughtObjectContainer;
- private readonly Container caughtFruitContainer;
+ ///
+ /// Contains objects dropped from the plate.
+ ///
+ private readonly Container droppedObjectTarget;
public CatcherAnimationState CurrentState { get; private set; }
@@ -108,7 +114,11 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool hitExplosionPool;
private readonly Container hitExplosionContainer;
- public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null)
+ private readonly DrawablePool caughtFruitPool;
+ private readonly DrawablePool caughtBananaPool;
+ private readonly DrawablePool caughtDropletPool;
+
+ public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null)
{
this.trailsTarget = trailsTarget;
this.droppedObjectTarget = droppedObjectTarget;
@@ -124,7 +134,11 @@ namespace osu.Game.Rulesets.Catch.UI
InternalChildren = new Drawable[]
{
hitExplosionPool = new DrawablePool(10),
- caughtFruitContainer = new Container
+ caughtFruitPool = new DrawablePool(50),
+ caughtBananaPool = new DrawablePool(100),
+ // less capacity is needed compared to fruit because droplet is not stacked
+ caughtDropletPool = new DrawablePool(25),
+ caughtObjectContainer = new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
@@ -172,7 +186,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// Creates proxied content to be displayed beneath hitobjects.
///
- public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy();
+ public Drawable CreateProxiedContent() => caughtObjectContainer.CreateProxy();
///
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
@@ -215,10 +229,19 @@ namespace osu.Game.Rulesets.Catch.UI
catchResult.CatcherAnimationState = CurrentState;
catchResult.CatcherHyperDash = HyperDashing;
- if (!(drawableObject.HitObject is PalpableCatchHitObject hitObject)) return;
+ if (!(drawableObject is DrawablePalpableCatchHitObject palpableObject)) return;
+
+ var hitObject = palpableObject.HitObject;
if (result.IsHit)
- placeCaughtObject(hitObject);
+ {
+ var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X / 2);
+
+ placeCaughtObject(palpableObject, positionInStack);
+
+ if (hitLighting.Value)
+ addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
+ }
// droplet doesn't affect the catcher state
if (hitObject is TinyDroplet) return;
@@ -256,8 +279,8 @@ namespace osu.Game.Rulesets.Catch.UI
SetHyperDashState();
}
- caughtFruitContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
- droppedObjectTarget.RemoveAll(d => (d as DrawableCatchHitObject)?.HitObject == drawableObject.HitObject);
+ caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
+ droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
}
@@ -441,138 +464,121 @@ namespace osu.Game.Rulesets.Catch.UI
updateCatcher();
}
- private void placeCaughtObject(PalpableCatchHitObject source)
+ private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)
{
- var caughtObject = createCaughtObject(source);
+ var caughtObject = getCaughtObject(drawableObject.HitObject);
if (caughtObject == null) return;
- caughtObject.RelativePositionAxes = Axes.None;
- caughtObject.X = source.X - X;
- caughtObject.IsOnPlate = true;
-
+ caughtObject.CopyStateFrom(drawableObject);
caughtObject.Anchor = Anchor.TopCentre;
- caughtObject.Origin = Anchor.Centre;
- caughtObject.Scale *= 0.5f;
- caughtObject.LifetimeStart = source.StartTime;
- caughtObject.LifetimeEnd = double.MaxValue;
+ caughtObject.Position = position;
+ caughtObject.Scale /= 2;
- adjustPositionInStack(caughtObject);
-
- caughtFruitContainer.Add(caughtObject);
-
- addLighting(caughtObject);
+ caughtObjectContainer.Add(caughtObject);
if (!caughtObject.StaysOnPlate)
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
}
- private void adjustPositionInStack(DrawablePalpableCatchHitObject caughtObject)
+ private Vector2 computePositionInStack(Vector2 position, float displayRadius)
{
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)))
+ while (caughtObjectContainer.Any(f => Vector2Extensions.Distance(f.Position, position) < (displayRadius + radius_div_2) / (allowance / 2)))
{
- float diff = (caughtObjectRadius + radius_div_2) / allowance;
+ float diff = (displayRadius + radius_div_2) / allowance;
- caughtObject.X += (RNG.NextSingle() - 0.5f) * diff * 2;
- caughtObject.Y -= RNG.NextSingle() * diff;
+ position.X += (RNG.NextSingle() - 0.5f) * diff * 2;
+ position.Y -= RNG.NextSingle() * diff;
}
- caughtObject.X = Math.Clamp(caughtObject.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
+ position.X = Math.Clamp(position.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
+
+ return position;
}
- private void addLighting(DrawablePalpableCatchHitObject caughtObject)
+ private void addLighting(CatchHitObject hitObject, float x, Color4 colour)
{
- if (!hitLighting.Value) return;
-
HitExplosion hitExplosion = hitExplosionPool.Get();
- hitExplosion.HitObject = caughtObject.HitObject;
- hitExplosion.X = caughtObject.X;
- hitExplosion.Scale = new Vector2(caughtObject.HitObject.Scale);
- hitExplosion.ObjectColour = caughtObject.AccentColour.Value;
+ hitExplosion.HitObject = hitObject;
+ hitExplosion.X = x;
+ hitExplosion.Scale = new Vector2(hitObject.Scale);
+ hitExplosion.ObjectColour = colour;
hitExplosionContainer.Add(hitExplosion);
}
- private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source)
+ private CaughtObject getCaughtObject(PalpableCatchHitObject source)
{
switch (source)
{
- case Banana banana:
- return new DrawableBanana(banana);
+ case Fruit _:
+ return caughtFruitPool.Get();
- case Fruit fruit:
- return new DrawableFruit(fruit);
+ case Banana _:
+ return caughtBananaPool.Get();
- case TinyDroplet tiny:
- return new DrawableTinyDroplet(tiny);
-
- case Droplet droplet:
- return new DrawableDroplet(droplet);
+ case Droplet _:
+ return caughtDropletPool.Get();
default:
return null;
}
}
+ private CaughtObject getDroppedObject(CaughtObject caughtObject)
+ {
+ var droppedObject = getCaughtObject(caughtObject.HitObject);
+
+ droppedObject.CopyStateFrom(caughtObject);
+ droppedObject.Anchor = Anchor.TopLeft;
+ droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
+
+ return droppedObject;
+ }
+
private void clearPlate(DroppedObjectAnimation animation)
{
- var caughtObjects = caughtFruitContainer.Children.ToArray();
- caughtFruitContainer.Clear(false);
+ var droppedObjects = caughtObjectContainer.Children.Select(getDroppedObject).ToArray();
- droppedObjectTarget.AddRange(caughtObjects);
+ caughtObjectContainer.Clear(false);
- foreach (var caughtObject in caughtObjects)
- drop(caughtObject, animation);
+ droppedObjectTarget.AddRange(droppedObjects);
+
+ foreach (var droppedObject in droppedObjects)
+ applyDropAnimation(droppedObject, animation);
}
- private void removeFromPlate(DrawablePalpableCatchHitObject caughtObject, DroppedObjectAnimation animation)
+ private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
{
- if (!caughtFruitContainer.Remove(caughtObject))
- throw new InvalidOperationException("Can only drop a caught object on the plate");
+ var droppedObject = getDroppedObject(caughtObject);
- droppedObjectTarget.Add(caughtObject);
+ caughtObjectContainer.Remove(caughtObject);
- drop(caughtObject, animation);
+ droppedObjectTarget.Add(droppedObject);
+
+ applyDropAnimation(droppedObject, animation);
}
- private void drop(DrawablePalpableCatchHitObject d, DroppedObjectAnimation animation)
+ private void applyDropAnimation(Drawable 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)
{
- switch (animation)
- {
- case DroppedObjectAnimation.Drop:
- d.MoveToY(d.Y + 75, 750, Easing.InSine);
- d.FadeOut(750);
- break;
+ 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();
+ case DroppedObjectAnimation.Explode:
+ var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X;
+ 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
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 857d9141c9..44adbd5512 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.UI
public readonly Catcher MovableCatcher;
private readonly CatchComboDisplay comboDisplay;
- public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null)
+ public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null)
{
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
Children = new Drawable[]
diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
index d1c4a1c56d..783636a62d 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs
@@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
public abstract class DrawableTaikoRulesetTestScene : OsuTestScene
{
+ protected const int DEFAULT_PLAYFIELD_CONTAINER_HEIGHT = 768;
+
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
protected Container PlayfieldContainer { get; private set; }
@@ -44,10 +46,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
Add(PlayfieldContainer = new Container
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
- Height = 768,
+ Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
});
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs
index fb0917341e..f048cad18c 100644
--- a/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTestHit.cs
@@ -13,12 +13,15 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
public readonly HitResult Type;
- public DrawableTestHit(Hit hit, HitResult type = HitResult.Great)
+ public DrawableTestHit(Hit hit, HitResult type = HitResult.Great, bool kiai = false)
: base(hit)
{
Type = type;
- HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ var controlPoints = new ControlPointInfo();
+ controlPoints.Add(0, new EffectControlPoint { KiaiMode = kiai });
+
+ HitObject.ApplyDefaults(controlPoints, new BeatmapDifficulty());
}
protected override void UpdateInitialTransforms()
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
index e4c0766844..c3fa03d404 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs
@@ -2,14 +2,16 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Utils;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
@@ -97,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
break;
case 6:
- PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
+ PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, DEFAULT_PLAYFIELD_CONTAINER_HEIGHT), 500);
break;
}
}
@@ -106,13 +108,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
- var cpi = new ControlPointInfo();
- cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
-
Hit hit = new Hit();
- hit.ApplyDefaults(cpi, new BeatmapDifficulty());
-
- var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
+ var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h);
@@ -123,25 +120,27 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
- var cpi = new ControlPointInfo();
- cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
-
- Hit hit = new Hit();
- hit.ApplyDefaults(cpi, new BeatmapDifficulty());
-
- var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
+ Hit hit = new Hit
+ {
+ IsStrong = true,
+ Samples = createSamples(strong: true)
+ };
+ var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
- ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
+ ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
}
private void addMissJudgement()
{
DrawableTestHit h;
- DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
- ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
+ DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit { StartTime = DrawableRuleset.Playfield.Time.Current }, HitResult.Miss)
+ {
+ Alpha = 0
+ });
+ ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(h.HitObject, new TaikoJudgement()) { Type = HitResult.Miss });
}
private void addBarLine(bool major, double delay = scroll_time)
@@ -173,6 +172,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong,
+ Samples = createSamples(strong: strong),
Duration = duration,
TickRate = 8,
};
@@ -190,7 +190,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit h = new Hit
{
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
- IsStrong = strong
+ IsStrong = strong,
+ Samples = createSamples(HitType.Centre, strong)
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
@@ -203,7 +204,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit h = new Hit
{
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
- IsStrong = strong
+ IsStrong = strong,
+ Samples = createSamples(HitType.Rim, strong)
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
@@ -211,14 +213,18 @@ namespace osu.Game.Rulesets.Taiko.Tests
DrawableRuleset.Playfield.Add(new DrawableHit(h));
}
- private class TestStrongNestedHit : DrawableStrongNestedHit
+ // TODO: can be removed if a better way of handling colour/strong type and samples is developed
+ private IList createSamples(HitType? hitType = null, bool strong = false)
{
- public TestStrongNestedHit(DrawableHitObject mainObject)
- : base(new StrongHitObject { StartTime = mainObject.HitObject.StartTime }, mainObject)
- {
- }
+ var samples = new List();
- public override bool OnPressed(TaikoAction action) => false;
+ if (hitType == HitType.Rim)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
+
+ if (strong)
+ samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
+
+ return samples;
}
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
index c4563d5844..e90ccbb805 100644
--- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
@@ -104,7 +104,7 @@ namespace osu.Game.Beatmaps
string cacheFilePath = storage.GetFullPath(cache_database_name);
string compressedCacheFilePath = $"{cacheFilePath}.bz2";
- cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2");
+ cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2?{DateTimeOffset.UtcNow:yyyyMMdd}");
cacheDownloadRequest.Failed += ex =>
{
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 2c83161614..0248432917 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -95,6 +95,16 @@ namespace osu.Game.Screens.Play
localGameplayClock = new LocalGameplayClock(userOffsetClock);
GameplayClock.IsPaused.BindTo(IsPaused);
+
+ IsPaused.BindValueChanged(onPauseChanged);
+ }
+
+ private void onPauseChanged(ValueChangedEvent isPaused)
+ {
+ if (isPaused.NewValue)
+ this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => adjustableClock.Stop());
+ else
+ this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
}
private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset;
@@ -154,13 +164,16 @@ namespace osu.Game.Screens.Play
public void Start()
{
- // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
- // This accounts for the audio clock source potentially taking time to enter a completely stopped state
- Seek(GameplayClock.CurrentTime);
- adjustableClock.Start();
- IsPaused.Value = false;
+ if (!adjustableClock.IsRunning)
+ {
+ // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time
+ // This accounts for the audio clock source potentially taking time to enter a completely stopped state
+ Seek(GameplayClock.CurrentTime);
- this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In);
+ adjustableClock.Start();
+ }
+
+ IsPaused.Value = false;
}
///
@@ -199,8 +212,6 @@ namespace osu.Game.Screens.Play
public void Stop()
{
- this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => adjustableClock.Stop());
-
IsPaused.Value = true;
}
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 42074ac241..729119fa36 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
@@ -71,8 +72,9 @@ namespace osu.Game.Screens.Play
}
private bool readyForPush =>
+ !playerConsumed
// don't push unless the player is completely loaded
- player?.LoadState == LoadState.Ready
+ && player?.LoadState == LoadState.Ready
// don't push if the user is hovering one of the panes, unless they are idle.
&& (IsHovered || idleTracker.IsIdle.Value)
// don't push if the user is dragging a slider or otherwise.
@@ -84,6 +86,11 @@ namespace osu.Game.Screens.Play
private Player player;
+ ///
+ /// Whether the curent player instance has been consumed via .
+ ///
+ private bool playerConsumed;
+
private LogoTrackingContainer content;
private bool hideOverlays;
@@ -179,7 +186,10 @@ namespace osu.Game.Screens.Play
contentIn();
MetadataInfo.Delay(750).FadeIn(500);
- this.Delay(1800).Schedule(pushWhenLoaded);
+
+ // after an initial delay, start the debounced load check.
+ // this will continue to execute even after resuming back on restart.
+ Scheduler.Add(new ScheduledDelegate(pushWhenLoaded, 1800, 0));
showMuteWarningIfNeeded();
}
@@ -188,17 +198,18 @@ namespace osu.Game.Screens.Play
{
base.OnResuming(last);
- contentIn();
+ // prepare for a retry.
+ player = null;
+ playerConsumed = false;
+ cancelLoad();
- this.Delay(400).Schedule(pushWhenLoaded);
+ contentIn();
}
public override void OnSuspending(IScreen next)
{
base.OnSuspending(next);
- cancelLoad();
-
BackgroundBrightnessReduction = false;
// we're moving to player, so a period of silence is upcoming.
@@ -274,6 +285,14 @@ namespace osu.Game.Screens.Play
}
}
+ private Player consumePlayer()
+ {
+ Debug.Assert(!playerConsumed);
+
+ playerConsumed = true;
+ return player;
+ }
+
private void prepareNewPlayer()
{
if (!this.IsCurrentScreen())
@@ -315,64 +334,62 @@ namespace osu.Game.Screens.Play
{
if (!this.IsCurrentScreen()) return;
- try
+ if (!readyForPush)
{
- if (!readyForPush)
+ // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
+ // if we become unready for push during the delay.
+ cancelLoad();
+ return;
+ }
+
+ // if a push has already been scheduled, no further action is required.
+ // this value is reset via cancelLoad() to allow a second usage of the same PlayerLoader screen.
+ if (scheduledPushPlayer != null)
+ return;
+
+ scheduledPushPlayer = Scheduler.AddDelayed(() =>
+ {
+ // ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
+ var consumedPlayer = consumePlayer();
+
+ contentOut();
+
+ TransformSequence pushSequence = this.Delay(250);
+
+ // only show if the warning was created (i.e. the beatmap needs it)
+ // and this is not a restart of the map (the warning expires after first load).
+ if (epilepsyWarning?.IsAlive == true)
{
- // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
- // if we become unready for push during the delay.
- cancelLoad();
- return;
+ const double epilepsy_display_length = 3000;
+
+ pushSequence
+ .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible)
+ .TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint)
+ .Delay(epilepsy_display_length)
+ .Schedule(() =>
+ {
+ epilepsyWarning.Hide();
+ epilepsyWarning.Expire();
+ })
+ .Delay(EpilepsyWarning.FADE_DURATION);
}
- if (scheduledPushPlayer != null)
- return;
-
- scheduledPushPlayer = Scheduler.AddDelayed(() =>
+ pushSequence.Schedule(() =>
{
- contentOut();
+ if (!this.IsCurrentScreen()) return;
- TransformSequence pushSequence = this.Delay(250);
+ LoadTask = null;
- // only show if the warning was created (i.e. the beatmap needs it)
- // and this is not a restart of the map (the warning expires after first load).
- if (epilepsyWarning?.IsAlive == true)
- {
- const double epilepsy_display_length = 3000;
+ // By default, we want to load the player and never be returned to.
+ // Note that this may change if the player we load requested a re-run.
+ ValidForResume = false;
- pushSequence
- .Schedule(() => epilepsyWarning.State.Value = Visibility.Visible)
- .TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint)
- .Delay(epilepsy_display_length)
- .Schedule(() =>
- {
- epilepsyWarning.Hide();
- epilepsyWarning.Expire();
- })
- .Delay(EpilepsyWarning.FADE_DURATION);
- }
-
- pushSequence.Schedule(() =>
- {
- if (!this.IsCurrentScreen()) return;
-
- LoadTask = null;
-
- // By default, we want to load the player and never be returned to.
- // Note that this may change if the player we load requested a re-run.
- ValidForResume = false;
-
- if (player.LoadedBeatmapSuccessfully)
- this.Push(player);
- else
- this.Exit();
- });
- }, 500);
- }
- finally
- {
- Schedule(pushWhenLoaded);
- }
+ if (consumedPlayer.LoadedBeatmapSuccessfully)
+ this.Push(consumedPlayer);
+ else
+ this.Exit();
+ });
+ }, 500);
}
private void cancelLoad()
@@ -390,7 +407,7 @@ namespace osu.Game.Screens.Play
if (isDisposing)
{
// if the player never got pushed, we should explicitly dispose it.
- DisposalTask = LoadTask?.ContinueWith(_ => player.Dispose());
+ DisposalTask = LoadTask?.ContinueWith(_ => player?.Dispose());
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 9d37ceee6c..b4c7dca12f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -26,7 +26,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index ab03393836..7542aded86 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -88,7 +88,7 @@
-
+