Skip to content

Fix fruit positions getting mangled when exploded from the plate #29403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ public void TestHitLightingColour()
{
AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddAssert("correct hit lighting colour", () => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
AddAssert("correct hit lighting colour",
() => catcher.ChildrenOfType<HitExplosion>().First()?.Entry?.ObjectColour == this.ChildrenOfType<DrawableCatchHitObject>().First().AccentColour.Value);
}

[Test]
Expand All @@ -259,6 +260,16 @@ public void TestHitLightingDisabled()
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
}

[Test]
public void TestAllExplodedObjectsAtUniquePositions()
{
AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
AddStep("catch normal fruit", () => attemptCatch(new Fruit { IndexInBeatmap = 2, LastInCombo = true }));
AddAssert("two fruit at distinct x coordinates",
() => this.ChildrenOfType<CaughtFruit>().Select(f => f.DrawPosition.X).Distinct(),
() => Has.Exactly(2).Items);
}

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);
Expand Down
28 changes: 12 additions & 16 deletions osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ public abstract partial class CaughtObject : SkinnableDrawable, IHasCatchObjectS
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();

public Vector2 DisplayPosition => DrawPosition;
public Vector2 DisplaySize => Size * Scale;

public float DisplayRotation => Rotation;

public double DisplayStartTime => HitObject.StartTime;

/// <summary>
Expand All @@ -44,25 +42,23 @@ protected CaughtObject(CatchSkinComponents skinComponent, Func<ISkinComponentLoo
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
}

/// <summary>
/// Copies the hit object visual state from another <see cref="IHasCatchObjectState"/> object.
/// </summary>
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;
IndexInBeatmap.Value = objectState.IndexInBeatmap.Value;
}

protected override void FreeAfterUse()
{
ClearTransforms();
Alpha = 1;

base.FreeAfterUse();
}

public void RestoreState(CatchObjectState state)
{
HitObject = state.HitObject;
AccentColour.Value = state.AccentColour;
HyperDash.Value = state.HyperDash;
IndexInBeatmap.Value = state.IndexInBeatmap;
Position = state.DisplayPosition;
Scale = Vector2.Divide(state.DisplaySize, Size);
Rotation = state.DisplayRotation;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public abstract partial class DrawablePalpableCatchHitObject : DrawableCatchHitO
/// </summary>
protected readonly Container ScalingContainer;

public Vector2 DisplayPosition => DrawPosition;

public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;

public float DisplayRotation => ScalingContainer.Rotation;
Expand Down Expand Up @@ -95,5 +97,7 @@ protected override void OnFree()

base.OnFree();
}

public void RestoreState(CatchObjectState state) => throw new NotSupportedException("Cannot restore state into a drawable catch hitobject.");
}
}
32 changes: 25 additions & 7 deletions osu.Game.Rulesets.Catch/Objects/Drawables/IHasCatchObjectState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,35 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
public interface IHasCatchObjectState
{
PalpableCatchHitObject HitObject { get; }

double DisplayStartTime { get; }

Bindable<Color4> AccentColour { get; }

Bindable<bool> HyperDash { get; }

Bindable<int> IndexInBeatmap { get; }

double DisplayStartTime { get; }
Vector2 DisplayPosition { get; }
Vector2 DisplaySize { get; }

float DisplayRotation { get; }

void RestoreState(CatchObjectState state);
}

public static class HasCatchObjectStateExtensions
{
public static CatchObjectState SaveState(this IHasCatchObjectState target) => new CatchObjectState(
target.HitObject,
target.AccentColour.Value,
target.HyperDash.Value,
target.IndexInBeatmap.Value,
target.DisplayPosition,
target.DisplaySize,
target.DisplayRotation);
}

public readonly record struct CatchObjectState(
PalpableCatchHitObject HitObject,
Color4 AccentColour,
bool HyperDash,
int IndexInBeatmap,
Vector2 DisplayPosition,
Vector2 DisplaySize,
float DisplayRotation);
}
42 changes: 26 additions & 16 deletions osu.Game.Rulesets.Catch/UI/Catcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Buffers;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
Expand Down Expand Up @@ -362,7 +363,7 @@ private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Ve

if (caughtObject == null) return;

caughtObject.CopyStateFrom(drawableObject);
caughtObject.RestoreState(drawableObject.SaveState());
caughtObject.Anchor = Anchor.TopCentre;
caughtObject.Position = position;
caughtObject.Scale *= caught_fruit_scale_adjust;
Expand Down Expand Up @@ -411,41 +412,50 @@ private void addLighting(JudgementResult judgementResult, Color4 colour, float x
}
}

private CaughtObject getDroppedObject(CaughtObject caughtObject)
private CaughtObject getDroppedObject(CatchObjectState state)
{
var droppedObject = getCaughtObject(caughtObject.HitObject);
var droppedObject = getCaughtObject(state.HitObject);
Debug.Assert(droppedObject != null);

droppedObject.CopyStateFrom(caughtObject);
droppedObject.RestoreState(state);
droppedObject.Anchor = Anchor.TopLeft;
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(state.DisplayPosition, droppedObjectTarget);

return droppedObject;
}

private void clearPlate(DroppedObjectAnimation animation)
{
var caughtObjects = caughtObjectContainer.Children.ToArray();
int caughtCount = caughtObjectContainer.Children.Count;
CatchObjectState[] states = ArrayPool<CatchObjectState>.Shared.Rent(caughtCount);

caughtObjectContainer.Clear(false);

// Use the already returned PoolableDrawables for new objects
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
try
{
for (int i = 0; i < caughtCount; i++)
states[i] = caughtObjectContainer.Children[i].SaveState();

droppedObjectTarget.AddRange(droppedObjects);
caughtObjectContainer.Clear(false);

foreach (var droppedObject in droppedObjects)
applyDropAnimation(droppedObject, animation);
for (int i = 0; i < caughtCount; i++)
{
CaughtObject obj = getDroppedObject(states[i]);
droppedObjectTarget.Add(obj);
applyDropAnimation(obj, animation);
}
}
finally
{
ArrayPool<CatchObjectState>.Shared.Return(states);
}
}

private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
{
CatchObjectState state = caughtObject.SaveState();
caughtObjectContainer.Remove(caughtObject, false);

var droppedObject = getDroppedObject(caughtObject);

var droppedObject = getDroppedObject(state);
droppedObjectTarget.Add(droppedObject);

applyDropAnimation(droppedObject, animation);
}

Expand Down
Loading