Advertisement

UDP Multiplayer Movement Jitter.

Started by September 25, 2014 02:52 PM
7 comments, last by rip-off 10 years, 4 months ago

I've started work on a basic grid based multilayer game.

The game is made to run at 15fps, so data is sent around every 66ms.

Right now what I've got working is clients joining, getting a player spawned, and a start on movement.

Everything works fine as long as your round trip for a packet is less then 66ms. after that movement gets jittery.

That was expected though, because of how I'm handling the movement.....

Here's basically how it works:

  1. Client 1 tries to move the player in a direction.
  2. Client 1 checks if it can move the player
  3. Client 1 sends the request to the server
  4. Client 1 moves its player.
  5. Server checks if the player can move.
  6. Server moves the player
  7. Server sends all clients the new position of the player
  8. Client 1 moves the player to the position the server sent back (if it has to).

A basic authoritative server, right?

If the client moves the player more than once before the server returns with first movements data,

the client will think its in the wrong place, thus the player jumps around between grid spaces. (jitter)

The solution would be to keep track of a player's movements and match them up with the proper packets (by ID-ing them I suppose)

but what if packets are dropped?

What if the packets return out of order?

oh and, I've read this article http://www.gabrielgambetta.com/fpm2.html.

which describes my situation pretty well....

I'm just really unsure of how its implemented.

Thank you for the reading/helping!

[twitter]DustedHam[/twitter]

I think what you would be looking for is a very simple incrementing ID system. Basically, when the server sends out an updated position, the first time it will have an ID of 1. Then the next time it is 2. 2 is greater than 1, so we can update our client based on the new position. Then the server sends out packet 3, but it never reaches the client. Then the server sends out 4, which reaches the client. 4 is greater than 2, so update the position. Then 3 manages to find its way to the client. 3 is not greater than 4, so ignore it.

Advertisement

Wow... I kinda feel like an idiot, lol.

defiantly thought of giving them IDs, for some reason never occurred to me to actually check the order based on the ID number....

thank you.

[twitter]DustedHam[/twitter]

Roughly speaking, there are two qualities to a message relevant here: reliable and ordered. Reliable guarantees a message is delivered. Ordered guarantees that messages are processed sequentially.

Here, you wanted unreliable ordered messages (I don't care if you lose an update, but the ones I get must be processed in order). A delayed packet can be considered the same as a lost packet.

There are other cases where you might want reliable unordered messages, reliable ordered messages, or unreliable unordered messages.

There can also be a concept of channels. This allows you to have groups of messages ordered relative to each other on the same channel but not between the other ordered channels.

Position updates could be ordered relative to themselves for instance but don't need to be ordered against other kinds of messages.

Channel sequence IDs deal with ordering. ACKs deal with reliable delivery. You can have one or both.

You can also have variable reliability. For instance if you get sequence numbers 2 & 4 on a semi-unreliable channel you could hold 4 in a wueue for a short time just in case 3 is on the way. Likewise, you can have a limited number of retries for unACKed messages before you stop resenting it. This can make protocols that can deal with lost messages but operate best without any losses behave a bit better. These settings should be per-channel of course.

Getting your movement smooth is unfortunately much harder than hacks in the network protocol. The game itself has to be built to hide latency and smooth over discontinuity. This often involved chsnges to both art and design as well as engineering work in one or more of client-side movement interpolation, client-side prediction, and server-side history playback, plus a few other options.

Sean Middleditch – Game Systems Engineer – Join my team!


The game is made to run at 15fps, so data is sent around every 66ms.

It is a generally a bad idea to couple your logical update rate to your frame rate. The recommendation is have a fixed timestep.


Everything works fine as long as your round trip for a packet is less then 66ms. after that movement gets jittery.

Unfortunately, you're running up against one of the biggest known physical constraint: the speed of light. Moving data Internet distances can take time, and 66ms round trip is 33ms one way with no processing delay. When I used to play multi-player action games, I'd be doing well to have a one-way time of about 40ms, and values of 60/70 weren't uncommon.

There are two general approaches:

  • Allow the client to locally initiate actions based on assumed server state, but require that the server can overrule it later on.
  • Require all actions be acknowledged by the server before the client takes action - the client understands the "current" server state exactly
  • The former is virtually a requirement for all fast-paced action games.

    In the first model, the client has a local copy of the last known server state, from which it interpolates to display and to predict consequences. Most of the time this is reasonably accurate, and the result is no delay for the client. However, temporal anomalies can and do occur during periods of poor latency, the player might think they are moving or firing but the server later on overrules the decision, resulting in "warping" and "missed shots" that the player disagrees with. Unhappily, moments of high unpredictability are often those the player values most: a big fire-fight for example!

    In the second model, the main task is to try to hide this detail from the user. For example, in RTS games, the units might say "Yes, commander" before taking action. The time taken to say this generally is greater than the time taken for the server to OK the command, so the client appears smooth to the player.

    It isn't clear to me exactly the style of game you have, as this usually has a large impact on the feasible networking strategies - but it sounds like your game implementation hasn't taken a definite stance on this issue, and as a result your implementation allows for strange, inconsistent behaviour.

    While technically unrelated, these two approaches tend to favour UDP and TCP respectively. If packets being lost or out-of-order is a concern, then using UDP is probably the wrong decision. Only use UDP if you aren't going to build a poor person's TCP on top of it. That said, UDP still has value in semi-reliable situations, as SeanMiddleditch covered.

    Thanks for the information, looks like I have a few changes to make....

    I'm trying to make a fast paced game, and to be honest this is my first crack at using UDP, I figured I would run into a lot of problems.

    • Allow the client to locally initiate actions based on assumed server state, but require that the server can overrule it later on.

    That's my current goal... Right now I'm storing the clients movements and running them

    then checking the packets against movements when the packets come in.

    so movement packet 132 only checks against the clients movement 132

    Is that the wrong way to approach this? (I may be explaining it poorly)

    [twitter]DustedHam[/twitter]

    Advertisement

    Well, you would keep a "running" id.

    If the incoming packet from the server has an id less than or equal to the id of the last packet received, just ignore it.

    This will prevent reordering from jacking up your movements. Each accepted packet id becomes the new running id (and not the id you just sent).

    However, server packets may arrive at inconsistant latency, which will make players "rubber-band" around if you render directly from the networks position since the position will snap back from the last client position that the server hasn't processed yet and the last server position thats old.

    To smooth out the position of players, you need to have two seperate copies of the position.

    The network copy that you move around and that the server updates, and another copy that you use to render from.

    The render copy is derived from smoothing out the network copy.

    One easy way can be as simple as just

    renderpos.x += ( netpos.x - renderpos.x ) * 0.3f //do the same for y and z of course

    This will make movement smooth and hide those sudden jerks when your network lags.

    More complicated methods involve interpolating between the last position before the last position and the current position based on the time elapsed.

    One downside of smoothing is that it makes a noticeable delay from when you press the key and when the character moves.

    To defeat that, only smooth the other players and render the first person player from the clients copy (not the servers copy) and syncing it only when the difference is extreme.

    You take the servers response, compare it and ignore it if its within a certain distance and time from the local copy- if not use the servers value.

    Thanks for all the replies and info, I seem to have things running pretty smooth!

    [twitter]DustedHam[/twitter]

    Are you simulating poor network conditions? How are you handling packet loss? Does the server send full-gamestate information regularly?

    This topic is closed to new replies.

    Advertisement