Will "Rubber banding" resolve multiplayer interpolation stutter?
Asked Answered
A

2

12

I wrote multiplayer Pong using UDP. I am using interpolation and extrapolation in order to create a smooth looking effect on the client.

It works. However, there is a bit of constant stuttering in the ball. It jumps a tiny bit forward every time a new packet is received. It looks a little laggy, but it is playable.

There must be a way to make the game look smoother. I've read about Rubber Banding. What would be the best way to move from here?

I hope someone who is able to answer my question well will find it.

Update

As requested by Ivan, here is a graph of the ping times. However, I do believe that the problem exists inside the client smoothing code.

enter image description here

Abbe answered 11/10, 2016 at 12:7 Comment(12)
By the way, did you measure ping / fps?Federalize
@Federalize I am measuring Ping, yes. However, I am not using it at the moment.Abbe
I am asking about those metrics so we can quantify what 'a little laggy' is and in which circumstances. E.g. if your ping is 900ms basically any experience is okFederalize
It would be interesting to measure stutter problems over time and overlay that with your current ping-plot. Do the spikes co-incide? How much correction was needed at each point in time?Wickman
@Wickman The stutter problems do, very much, occur at the time where the ping spikes were high. I remember the in-game time when this happened and can see that these spikes are plotted exactly at the same time.Abbe
Do you have a central server (possibly co-located with a client) that is coordinating game-play, or are all clients on equal footing?Wickman
I am using a peer-to-pear connection. One of the clients acts as server and sends the game state at a fixed time step (20Hz) to the other peer.Abbe
Why keep a fixed time-step? Why not send only updates?Wickman
Thanks for the details. @Abbe can you please share some details on your client side code? How many updates do you buffer for the interpolation? Does the client predict position by itself or just displays server updates? If the client discovered predicted position calculated differs from what just received from the server, do you update user observed effect immediately or spread that over time with smoothing?Federalize
@Federalize I buffer 2 game states and interpolate between these 2. I extrapolate if there hasn't been pushed a new state. This extrapolation is the prediction. If there is any difference, I update it directly. I am not sure what algorithm to use for spreading / smoothing it.Abbe
@Abbe When you detect the difference, you should note the time (m_flPredictionErrorTime). Then you pick up some time over which the smoothing will happen cl_smoothtime. Somewhere close to the display code you calculate how much of error you're going to display errorAmount = ( currentTimeMillis() - m_flPredictionErrorTime ) / cl_smoothtime. Multiply your differences vector over that vOffset = m_vecPredictionError * errorAmount and add to the vector of the parameters (x, y, speed, ...). Once errorAmount is greater than 1, you stop considering it (full delta has been displayed)Federalize
@Abbe my comment from the above is retelling of GetPredictionErrorSmoothingVector function from Valve's Source SDK code. Please share how that worked for you or ask follow up questionsFederalize
W
6

Filling in with context from your previous question, I understand that you are sending paddle & ball positions from each client to the other. However, as long as the clients agree on where the paddles are at each moment in time, the ball's movement is completely determined (barring rounding errors), and you should experiment zero ball-stutter. Each client should keep its own internal state with the positions and speeds of paddles and the ball. Pseudocode would be similar to the following:

// input thread
if input changed,
   alter paddle speed and/or direction
   send timestamped message to inform my opponent of paddle change

// incoming network thread
if paddle packet received
   alter opponent's paddle speed and/or direction at time it was sent
   fix any errors in previously extrapolated paddle position <--- Easy
if ball-packet received
   fix any errors in ball position and speed <--- Tricky

// update entities thread
for each entity (my paddle, opponent paddle, the ball)
   compute updated entity position, adjusted by time-since-last-update
   if ball reached my end, send ball-packet to other side
   draw updated entity

This assumes that two package types are being exchanged:

  • paddle packets are timestamped positions + speeds of paddles, and are sent whenever a client alters the speed of its own paddle.
  • ball packets are timestamped positions + speeds of the ball, and are sent whenever a ball reaches a client's (local) side, whether it bounces off the paddle or not.

The pseudocode is performing extrapolation ("assume things keep on moving as usual") for all unknowns in the update-entities thread. The only point where problems arise is marked with <--- arrows.

You can easily correct for paddle positions by warping them to their new position, possibly interpolating the movement over a short period to make it less jarring.

Correcting for ball positions is easy if both clients more-or-less agree (and then you can do the interpolation trick again, to smoothen it up further). However, one client may see a near miss and another a near hit. In this case, since you are using a peer-to-peer model, we are letting the local client make the call, and explain what happened to the opponent (in another design, you would have a central server making these kinds of decisions; this is good to avoid cheating). You cannot avoid an ugly jump there if both clients disagree - but hopefully, this should be relatively rare and short, unless it coincides with a ping spike.

Wickman answered 14/10, 2016 at 13:46 Comment(2)
Hi, thank you for your answer. That is not my previous question. How do I avoid these jumps if both clients disagree? That is my questionAbbe
And my answer is that you are exchanging the wrong type of information far too frequently, leading to frequent and avoidable disagreements. You should exchange information only from the client that knows something to the one that does not yet know it, and then trust incoming information. Client B cannot know what paddle movements Client A has recently done due to network delays. Client B should therefore not tell Client A anything regarding Client A's movements.Wickman
F
2

One of the ideas which allows to get rid of this effect is Using smoothing when applying error prediction corrections on client.

How that works

At some point in your code you identify that ball position and client are different.

Discrepancy

Instead of applying that as a correction to client code immediately (which is one reason you can see those jumps), you perform that over some time, cl_smoothtime e.g. 500ms.

At first your program should store time when the error detection event occured m_flPredictionErrorTime.

public void onErrorDetected() {
    this.m_flPredictionErrorTime = System.currentTimeMillis();
}

Somewhere close to the display code you calculate how much of error you're going to display. Here's some pseudocode for that.

public void draw() {
    Point preditctionError = this.clientPredictedBallCoordinates - this.serverCoordinates;
    Point deltaToDisplay = calculateErrorVector(preditctionError);
    Point positionToDisplay = clientPredictedBallCoordinates + deltaToDisplay;
    // actually draw the ball here
}

public Point calculateErrorVector(Point coordinatesDelta) {
     double errorAmount = ( System.currentTimeMillis() - this.m_flPredictionErrorTime ) / this.cl_smoothtime.
     if (errorAmount > 1.0) {
         // whole difference applied in full, so returning zero delta
         return new Point(0,0);
     }
     if (errorAmount < 0) {
         // no errors detected yet so return zero delta
         return new Point(0,0);
     }
     Point delta = new Point(coordinates.x*errorAmount, coordinates.y*errorAmount);
     return delta;
}

I've picked this idea from Source Multiplayer Networking wiki. Actual code example in Cpp is available in their SDK around GetPredictionErrorSmoothingVector function.

Federalize answered 20/10, 2016 at 11:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.