diff --git a/osu.Android.props b/osu.Android.props
index 454bb46059..ec223f98c2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index a3307c9224..6abfbdbe21 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
AddSliderStep("circle size", 0, 8, 5, createCatcher);
AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t)));
+ AddToggleStep("toggle hit lighting", lighting => config.SetValue(OsuSetting.HitLighting, lighting));
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
AddStep("catch many random fruit", () =>
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
index e736d68740..371e901c69 100644
--- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Catch
Banana,
Droplet,
Catcher,
- CatchComboCounter
+ CatchComboCounter,
+ HitExplosion
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
new file mode 100644
index 0000000000..e1fad564a3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
@@ -0,0 +1,129 @@
+// 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.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Utils;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ private CircularContainer largeFaint;
+ private CircularContainer smallFaint;
+ private CircularContainer directionalGlow1;
+ private CircularContainer directionalGlow2;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+
+ // scale roughly in-line with visual appearance of notes
+ const float initial_height = 10;
+
+ InternalChildren = new Drawable[]
+ {
+ largeFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ },
+ smallFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ },
+ directionalGlow1 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ },
+ directionalGlow2 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ }
+ };
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ X = entry.Position;
+ Scale = new Vector2(entry.HitObject.Scale);
+ setColour(entry.ObjectColour);
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ applyTransforms(entry.HitObject.RandomSeed);
+ }
+
+ private void applyTransforms(int randomSeed)
+ {
+ const double duration = 400;
+
+ // we want our size to be very small so the glow dominates it.
+ largeFaint.Size = new Vector2(0.8f);
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+
+ const float angle_variangle = 15; // should be less than 45
+ directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
+ directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
+
+ this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
+ }
+
+ private void setColour(Color4 objectColour)
+ {
+ const float roundness = 100;
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ };
+
+ smallFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ };
+
+ directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
+ Roundness = roundness,
+ Radius = 40,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 5e744ec001..10fc4e78b2 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -70,13 +70,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
if (version < 2.3m)
{
- if (GetTexture(@"fruit-ryuuta") != null ||
- GetTexture(@"fruit-ryuuta-0") != null)
+ if (hasOldStyleCatcherSprite())
return new LegacyCatcherOld();
}
- if (GetTexture(@"fruit-catcher-idle") != null ||
- GetTexture(@"fruit-catcher-idle-0") != null)
+ if (hasNewStyleCatcherSprite())
return new LegacyCatcherNew();
return null;
@@ -86,12 +84,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
return new LegacyCatchComboCounter(Skin);
return null;
+
+ case CatchSkinComponents.HitExplosion:
+ if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
+ return new LegacyHitExplosion();
+
+ return null;
}
}
return base.GetDrawableComponent(component);
}
+ private bool hasOldStyleCatcherSprite() =>
+ GetTexture(@"fruit-ryuuta") != null
+ || GetTexture(@"fruit-ryuuta-0") != null;
+
+ private bool hasNewStyleCatcherSprite() =>
+ GetTexture(@"fruit-catcher-idle") != null
+ || GetTexture(@"fruit-catcher-idle-0") != null;
+
public override IBindable GetConfig(TLookup lookup)
{
switch (lookup)
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs
new file mode 100644
index 0000000000..c262b0a4ac
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs
@@ -0,0 +1,94 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Legacy
+{
+ public class LegacyHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ [Resolved]
+ private Catcher catcher { get; set; }
+
+ private const float catch_margin = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2;
+
+ private readonly Sprite explosion1;
+ private readonly Sprite explosion2;
+
+ public LegacyHitExplosion()
+ {
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ RelativeSizeAxes = Axes.Both;
+ Scale = new Vector2(0.5f);
+
+ InternalChildren = new[]
+ {
+ explosion1 = new Sprite
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.CentreLeft,
+ Alpha = 0,
+ Blending = BlendingParameters.Additive,
+ Rotation = -90
+ },
+ explosion2 = new Sprite
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.CentreLeft,
+ Alpha = 0,
+ Blending = BlendingParameters.Additive,
+ Rotation = -90
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(SkinManager skins)
+ {
+ var defaultLegacySkin = skins.DefaultLegacySkin;
+
+ // sprite names intentionally swapped to match stable member naming / ease of cross-referencing
+ explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
+ explosion2.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-1");
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ Colour = entry.ObjectColour;
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ {
+ float halfCatchWidth = catcher.CatchWidth / 2;
+ float explosionOffset = Math.Clamp(entry.Position, -halfCatchWidth + catch_margin * 3, halfCatchWidth - catch_margin * 3);
+
+ if (!(entry.HitObject is Droplet))
+ {
+ float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
+
+ explosion1.Scale = new Vector2(1, 0.9f);
+ explosion1.Position = new Vector2(explosionOffset, 0);
+
+ explosion1.FadeOutFromOne(300);
+ explosion1.ScaleTo(new Vector2(16 * scale, 1.1f), 160, Easing.Out);
+ }
+
+ explosion2.Scale = new Vector2(0.9f, 1);
+ explosion2.Position = new Vector2(explosionOffset, 0);
+
+ explosion2.FadeOutFromOne(700);
+ explosion2.ScaleTo(new Vector2(0.9f, 1.3f), 500, Easing.Out);
+
+ this.Delay(700).FadeOutFromOne();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 9fd4610e6e..5cd85aac56 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -23,6 +23,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
+ [Cached]
public class Catcher : SkinReloadableDrawable
{
///
@@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// Width of the area that can be used to attempt catches during gameplay.
///
- private readonly float catchWidth;
+ public readonly float CatchWidth;
private readonly SkinnableCatcher body;
@@ -133,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (difficulty != null)
Scale = calculateScale(difficulty);
- catchWidth = CalculateCatchWidth(Scale);
+ CatchWidth = CalculateCatchWidth(Scale);
InternalChildren = new Drawable[]
{
@@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (!(hitObject is PalpableCatchHitObject fruit))
return false;
- float halfCatchWidth = catchWidth * 0.5f;
+ float halfCatchWidth = CatchWidth * 0.5f;
return fruit.EffectiveX >= X - halfCatchWidth &&
fruit.EffectiveX <= X + halfCatchWidth;
}
@@ -216,7 +217,7 @@ namespace osu.Game.Rulesets.Catch.UI
placeCaughtObject(palpableObject, positionInStack);
if (hitLighting.Value)
- addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
+ addLighting(result, drawableObject.AccentColour.Value, positionInStack.X);
}
// droplet doesn't affect the catcher state
@@ -365,8 +366,8 @@ namespace osu.Game.Rulesets.Catch.UI
return position;
}
- private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
- hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
+ private void addLighting(JudgementResult judgementResult, Color4 colour, float x) =>
+ hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, judgementResult, colour, x));
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
{
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
index d9ab428231..955b1e6edb 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
@@ -1,129 +1,56 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Rulesets.Objects.Pooling;
-using osu.Game.Utils;
-using osuTK;
-using osuTK.Graphics;
+using osu.Game.Skinning;
+
+#nullable enable
namespace osu.Game.Rulesets.Catch.UI
{
public class HitExplosion : PoolableDrawableWithLifetime
{
- private readonly CircularContainer largeFaint;
- private readonly CircularContainer smallFaint;
- private readonly CircularContainer directionalGlow1;
- private readonly CircularContainer directionalGlow2;
+ private readonly SkinnableDrawable skinnableExplosion;
public HitExplosion()
{
- Size = new Vector2(20);
- Anchor = Anchor.TopCentre;
+ RelativeSizeAxes = Axes.Both;
+ Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
- // scale roughly in-line with visual appearance of notes
- const float initial_height = 10;
-
- InternalChildren = new Drawable[]
+ InternalChild = skinnableExplosion = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
{
- largeFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- },
- smallFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- },
- directionalGlow1 = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- },
- directionalGlow2 = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- }
+ CentreComponent = false,
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre
};
}
protected override void OnApply(HitExplosionEntry entry)
{
- X = entry.Position;
- Scale = new Vector2(entry.Scale);
- setColour(entry.ObjectColour);
-
- using (BeginAbsoluteSequence(entry.LifetimeStart))
- applyTransforms(entry.RNGSeed);
+ base.OnApply(entry);
+ if (IsLoaded)
+ apply(entry);
}
- private void applyTransforms(int randomSeed)
+ protected override void LoadComplete()
{
+ base.LoadComplete();
+ apply(Entry);
+ }
+
+ private void apply(HitExplosionEntry? entry)
+ {
+ if (entry == null)
+ return;
+
+ ApplyTransformsAt(double.MinValue, true);
ClearTransforms(true);
- const double duration = 400;
-
- // we want our size to be very small so the glow dominates it.
- largeFaint.Size = new Vector2(0.8f);
- largeFaint
- .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
- .FadeOut(duration * 2);
-
- const float angle_variangle = 15; // should be less than 45
- directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
- directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
-
- this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
- }
-
- private void setColour(Color4 objectColour)
- {
- const float roundness = 100;
-
- largeFaint.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- };
-
- smallFaint.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- };
-
- directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
- Roundness = roundness,
- Radius = 40,
- };
+ (skinnableExplosion.Drawable as IHitExplosion)?.Animate(entry);
+ LifetimeEnd = skinnableExplosion.Drawable.LatestTransformEndTime;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
index 094d88243a..6df13e52ef 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
@@ -1,6 +1,7 @@
// 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.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Objects.Pooling;
@@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
public HitExplosionContainer()
{
+ RelativeSizeAxes = Axes.Both;
+
AddInternal(pool = new DrawablePool(10));
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
index b142962a8a..88871c77f6 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
@@ -2,24 +2,42 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Performance;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Judgements;
using osuTK.Graphics;
+#nullable enable
+
namespace osu.Game.Rulesets.Catch.UI
{
public class HitExplosionEntry : LifetimeEntry
{
- public readonly float Position;
- public readonly float Scale;
- public readonly Color4 ObjectColour;
- public readonly int RNGSeed;
+ ///
+ /// The judgement result that triggered this explosion.
+ ///
+ public JudgementResult JudgementResult { get; }
- public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
+ ///
+ /// The hitobject which triggered this explosion.
+ ///
+ public CatchHitObject HitObject => (CatchHitObject)JudgementResult.HitObject;
+
+ ///
+ /// The accent colour of the object caught.
+ ///
+ public Color4 ObjectColour { get; }
+
+ ///
+ /// The position at which the object was caught.
+ ///
+ public float Position { get; }
+
+ public HitExplosionEntry(double startTime, JudgementResult judgementResult, Color4 objectColour, float position)
{
LifetimeStart = startTime;
Position = position;
- Scale = scale;
+ JudgementResult = judgementResult;
ObjectColour = objectColour;
- RNGSeed = rngSeed;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs b/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
new file mode 100644
index 0000000000..c744c00d9a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/IHitExplosion.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.
+
+#nullable enable
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ ///
+ /// Common interface for all hit explosion skinnables.
+ ///
+ public interface IHitExplosion
+ {
+ ///
+ /// Begins animating this .
+ ///
+ void Animate(HitExplosionEntry entry);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
index f6fd3e36ab..587ff4b573 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
@@ -9,6 +9,7 @@ using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
@@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private double lastTrailTime;
private IBindable cursorSize;
+ private Vector2? currentPosition;
+
public LegacyCursorTrail(ISkin skin)
{
this.skin = skin;
@@ -54,22 +57,34 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
}
protected override double FadeDuration => disjointTrail ? 150 : 500;
+ protected override float FadeExponent => 1;
protected override bool InterpolateMovements => !disjointTrail;
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!disjointTrail || !currentPosition.HasValue)
+ return;
+
+ if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
+ {
+ lastTrailTime = Time.Current;
+ AddTrail(currentPosition.Value);
+ }
+ }
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!disjointTrail)
return base.OnMouseMove(e);
- if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
- {
- lastTrailTime = Time.Current;
- return base.OnMouseMove(e);
- }
+ currentPosition = e.ScreenSpaceMousePosition;
+ // Intentionally block the base call as we're adding the trails ourselves.
return false;
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 7f86e9daf7..7a95111c91 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
private const int max_sprites = 2048;
+ ///
+ /// An exponentiating factor to ease the trail fade.
+ ///
+ protected virtual float FadeExponent => 1.7f;
+
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private int currentIndex;
private IShader shader;
@@ -141,22 +146,25 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override bool OnMouseMove(MouseMoveEvent e)
{
- Vector2 pos = e.ScreenSpaceMousePosition;
+ AddTrail(e.ScreenSpaceMousePosition);
+ return base.OnMouseMove(e);
+ }
- if (lastPosition == null)
+ protected void AddTrail(Vector2 position)
+ {
+ if (InterpolateMovements)
{
- lastPosition = pos;
- resampler.AddPosition(lastPosition.Value);
- return base.OnMouseMove(e);
- }
-
- foreach (Vector2 pos2 in resampler.AddPosition(pos))
- {
- Trace.Assert(lastPosition.HasValue);
-
- if (InterpolateMovements)
+ if (!lastPosition.HasValue)
{
- // ReSharper disable once PossibleInvalidOperationException
+ lastPosition = position;
+ resampler.AddPosition(lastPosition.Value);
+ return;
+ }
+
+ foreach (Vector2 pos2 in resampler.AddPosition(position))
+ {
+ Trace.Assert(lastPosition.HasValue);
+
Vector2 pos1 = lastPosition.Value;
Vector2 diff = pos2 - pos1;
float distance = diff.Length;
@@ -170,14 +178,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
addPart(lastPosition.Value);
}
}
- else
- {
- lastPosition = pos2;
- addPart(lastPosition.Value);
- }
}
-
- return base.OnMouseMove(e);
+ else
+ {
+ lastPosition = position;
+ addPart(lastPosition.Value);
+ }
}
private void addPart(Vector2 screenSpacePosition)
@@ -206,10 +212,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private Texture texture;
private float time;
+ private float fadeExponent;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 size;
-
private Vector2 originPosition;
private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1);
@@ -227,6 +233,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
texture = Source.texture;
size = Source.partSize;
time = Source.time;
+ fadeExponent = Source.FadeExponent;
originPosition = Vector2.Zero;
@@ -249,6 +256,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Bind();
shader.GetUniform("g_FadeClock").UpdateValue(ref time);
+ shader.GetUniform("g_FadeExponent").UpdateValue(ref fadeExponent);
texture.TextureGL.Bind();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 0ffa5209e3..8f20429bf0 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -25,7 +25,7 @@ using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
-using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Resources;
@@ -312,6 +312,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
@@ -348,6 +350,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddStep("restore beatmap", () =>
@@ -396,7 +400,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
- AddStep("open mod overlay", () => this.ChildrenOfType().ElementAt(2).TriggerClick());
+ AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick());
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 955be6ca21..ea10fc1b8b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -129,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Enabled.Value);
AddStep("click ready button", () =>
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index a53e253581..2616abf825 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -17,6 +17,7 @@ using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Screens.OnlinePlay.Match
{
@@ -250,5 +251,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
{
}
+
+ public class UserModSelectButton : PurpleTriangleButton
+ {
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 9fa19aaf21..a8e44dd56c 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- new PurpleTriangleButton
+ new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index 45aca24ab2..953c687087 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- new PurpleTriangleButton
+ new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index e6219fcb85..d4dba9330f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,8 +36,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9904946363..7e514afe74 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -93,7 +93,7 @@
-
+