2

To be brief, I am working on a real-time multiplayer game. In my game, the clients send updated position and velocity data to the server at a frequency of 20Hz. In the example code below, I am converting data from a Lua table into a C struct using LuaJIT FFI. It's a nifty way of transporting data over a network:

self.dt_f = self.dt_f + dt
if self.dt_f >= self.tick_f and self.id then
    self.dt_f = self.dt_f - self.tick_f

    local player = self.players[self.id]
    local data   = {
        type          = packets["player_update_f"],
        id            = self.id,
        position_x    = player.position.x,
        position_y    = player.position.y,
        position_z    = player.position.z,
        velocity_x    = player.velocity.x,
        velocity_y    = player.velocity.y,
        velocity_z    = player.velocity.z,
    }

    local struct = cdata:set_struct("player_update_f", data)
    local encoded = cdata:encode(struct)
    self.client:send(encoded)
end

When the server receives the packet, it tries to adjust the data to compensate for the latency between that particular client and itself:

local player        = self.players[id]
player.position     = update.position     or player.position
player.velocity     = update.velocity     or player.velocity

local server = self.server.connection.socket
local peer   = server:get_peer(id)
local ping   = peer:round_trip_time() / 2 / 1000

player.position = player.position + player.velocity * ping

Once the data is normalized, it then broadcasts the updated position info to all other clients:

local data = {
    type          = packets["player_update_f"],
    id            = id,
    position_x    = player.position.x,
    position_y    = player.position.y,
    position_z    = player.position.z,
    velocity_x    = player.velocity.x,
    velocity_y    = player.velocity.y,
    velocity_z    = player.velocity.z,
}
local struct  = cdata:set_struct("player_update_f", data)
local encoded = cdata:encode(struct)
self.server:send(encoded)

When the other clients finally get the packet, they adjust the data based on their latency with the server:

if id ~= self.id then
    self.players[id]          = self.players[id] or {}
    self.players[id].position = update.position  or self.players[id].position
    self.players[id].velocity = update.velocity  or self.players[id].velocity

    local ping = self.client.connection.peer:round_trip_time() / 2 / 1000
    self.players[id].position = self.players[id].position + self.players[id].velocity * ping
end

So herein lies the problem: The objects are very jittery. Every time I receive a packet, the other players warp a little forward or a little backward, so it seems like my latency compensation is off which makes my interpolation off. Perhaps someone could point out some obvious flaw in my code, or perhaps my understanding of how the process works?

Karai17
  • 923
  • 2
  • 9
  • 26

1 Answers1

2

For smooth animation, your server side position updates should take place on a fixed clock using the current values stored for your position/velocity vectors.

When a client update is received you need to make two calculations for the next tick:

  • First, using the client's vector, find the position the player should be at on the next tick.
  • Next, calculate a new vector for the server side player to reach that position and use that value to update the server velocity.

The server will then update all client positions in a very uniform way on the next tick. You can smooth the direction changes further by simply projecting 2 or more ticks into the future. The goal when trying to compensate for latency is really just to fall within an acceptable margin of error.

Ben Grimm
  • 4,316
  • 2
  • 15
  • 24