Ranked Play: Make cards draggable and reorderable#37157
Conversation
With a 25ms sample interval the replay recorder would frequently run out of buffer space and start dropping frames, so sample interval was increased to 50ms. Also buffed HandReplayPlayer.MaxQueuedFrames so the player won't immediately start dropping if a replay event arrives a bit too early during a drag.
osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Hand/HandOfCards.HandCard.cs
Fixed
Show fixed
Hide fixed
|
|
||
| Card.Elevation = float.Lerp(CardHovered ? 1 : 0, Card.Elevation, (float)Math.Exp(-0.03f * Time.Elapsed)); | ||
| if (MovementSpeed > 0) | ||
| Position = positionSpring.Update(Time.Elapsed * MovementSpeed, LayoutTarget.Position); |
There was a problem hiding this comment.
Man, this API structure matches nothing anywhere in our framework and i don't like it.
An update method returning a value? An interpolation not housed within the Interpolation class?
A value being provided to elapsed which is multiplied by some variable and no longer conveying elapsed time?
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa i dunnnno
There was a problem hiding this comment.
-
Spring Update method API
It's an update method because the spring is stateful (required because it needs to keep track of it's velocity).
Without tracking it's velocity you essentially just arrive atInterpolation.DampContinuouslyand lose all of the "springiness". -
Time.Elapsed being multiplied
MultipliplyingTime.Elapsedlike that is basically equivalent to applying the same operation onspring.NaturalFrequencyso I replaced it with that in 32fe4f3. -
Elevation interpolation formula
Turns out the interpolation for the card elevation was mathematically equivalent toInterpolation.DampContinuouslyso I replaced it with that now (cc02c6c).
The previous formula is equivalent to a halftime ofln(2) / 0.03so I rounded that to 25ms.
fwiw I originally picked up that formula from SSV1's beatmapset panel, and it's still being used in ScrollContainer.
There was a problem hiding this comment.
It's an update method because the spring is stateful (required because it needs to keep track of it's velocity).
This is fine, but having a return value in a method called Update goes against all my expectations.
osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/DiscardScreen.cs
Outdated
Show resolved
Hide resolved
| new HandReplayPlayer(matchInfo.OpponentId, opponentHand), | ||
| ]; | ||
|
|
||
| cardAddSample = audio.Samples.Get(@"Multiplayer/Matchmaking/Ranked/card-add-1"); |
There was a problem hiding this comment.
Did this come from somewhere else?
There was a problem hiding this comment.
This comes from the PickScreen. Noticed this was missing when going through both screens and making sure the screen-enter implementation for matches on both classes. Should have pointed this out, mb
There was a problem hiding this comment.
There's a lot of copy waste in this system, seeing the same thing changes in 4 places is fun. And finding things missed like this is double the fun.
osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/Hand/HandOfCards.cs
Outdated
Show resolved
Hide resolved
| positionSpring.Current = positionSpring.PreviousTarget = Position; | ||
| scaleSpring.Current = scaleSpring.PreviousTarget = 1; | ||
| rotationSpring.Current = rotationSpring.PreviousTarget = Rotation; |
There was a problem hiding this comment.
this feels super weird. i think this spring class needs more thought.
There was a problem hiding this comment.
The previous target value is required so springs can "react" to input changes (controlled via the Response parameter).
The card doesn't know it's starting position at construction time yet so the spring needs to be "reset" when that happens.
Perhaps adding a spring.Reset(value) method would be appropriate for this?
Alternatively just delaying creation of the springs until constructor time would be a possibility.
There was a problem hiding this comment.
Yes, I'd have preferred a Reset method (PreviousTarget being exposed for writing feels very wrong).
Delaying creation is also fine.
| ? new SpringParameters(2f, 0.4f, 1.2f) | ||
| : new SpringParameters(3f, 0.75f, 0.8f); |
There was a problem hiding this comment.
what does this mean? inline comment would be good
There was a problem hiding this comment.
Adjusted in 35be2a2.
Expanded the whole thing so names for all parameters being assigned are visible, and also added comments.
But TLDR:
- when card dragged -> rotation should be slow but springy to simulate sway
- when not dragged -> rotation should be snappy and have more damping, so it doesn't wobble around
To visualize the difference, this is what it would look like if the card always had the same parameters as during drag:
2026-04-01.00-25-39.mp4
| protected override CardLayout CalculateDraggedCardLayout(Vector2 dragPosition) | ||
| { | ||
| float maxExtent = TotalLayoutWidth / 2; | ||
|
|
||
| float x = float.Clamp(dragPosition.X, -maxExtent, maxExtent); | ||
| float arcRotation = GetArcRotation(x); | ||
|
|
||
| return new CardLayout | ||
| { | ||
| Position = GetArcPosition(x) + GetCardUpwardsDirection(arcRotation) * 60, | ||
| Rotation = 0, | ||
| Scale = HOVER_SCALE, | ||
| }; |
There was a problem hiding this comment.
None of this really makes sense to my weary eyes.
Needs much more commenting so I can discern why this is virtual-override and what anything means.
There was a problem hiding this comment.
Added comment in 35be2a2.
The purpose is so the opponent player cannot just drag his cards all over the screen.
Also replaced GetCardUpwardsDirection with just new Vector2(0, -60) since it just changes the x value by maybe 3 pixels while making the whole thing harder to understand. The original purpose of that was so the y offset is calculated the same way as in non-drag mode, where it actually matters since the card is rotated.
TLDR: it's to prevent this:
2026-04-01.00-30-30.mp4
and instead do this:
2026-04-01.00-31-54.mp4
c380a73 to
b379f91
Compare
b379f91 to
1cb0275
Compare
|
I'm blind merging this. At this poitn the code is so foreign and weird that this isn't going to make it worse. Let's just see if we can get something together. |
Adds the ability to drag and reorder cards. Card order is preserved between rounds and is synchronized between both players (each player can see the other player drag around and reorder their cards).
To make this possible I had to rewrite the card layout algorithm to be stateless (e0a46fe), there wasn't really a way around that since I needed a way to calculate a layout position based on a card's index. Should hopefully be a lot easier to read now though.
Some noteworthy stuff:
RankedPlayCardWithPlaylistItemsince that one will stay the same instance per round.OpponentHandOfCardswith a fixed y value.I also increased the buffer size in the replay player a bit so slight fluctuations in latency won't make it start to drop frames.
2026-03-31.01-19-27.mp4
2026-03-31.01-17-28.mp4