Компенсация задержки многопользовательской игры в реальном времени

Короче говоря, я работаю над многопользовательской игрой в реальном времени. В моей игре клиенты отправляют обновленные данные о положении и скорости на сервер с частотой 20 Гц. В приведенном ниже примере кода я преобразовываю данные из таблицы Lua в структуру C с использованием LuaJIT FFI. Это отличный способ передачи данных по сети:

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

Когда сервер получает пакет, он пытается настроить данные, чтобы компенсировать задержку между этим конкретным клиентом и самим собой:

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

Как только данные нормализуются, он затем передает обновленную информацию о местоположении всем другим клиентам:

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)

Когда другие клиенты, наконец, получают пакет, они корректируют данные в соответствии с их задержкой на сервере:

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

В этом и заключается проблема: объекты очень нервные. Каждый раз, когда я получаю пакет, другие игроки деформируются немного вперед или немного назад, поэтому кажется, что моя компенсация задержки отключена, что делает мою интерполяцию отключенной. Возможно, кто-то может указать на какой-то очевидный недостаток в моем коде, или, возможно, мое понимание того, как работает этот процесс?

1 ответ

Решение

Для плавной анимации ваши обновления положения на стороне сервера должны происходить в фиксированные часы с использованием текущих значений, сохраненных для ваших векторов положения / скорости.

Когда клиент получит обновление, вам нужно сделать два вычисления для следующего тика:

  • Сначала, используя вектор клиента, найдите позицию, в которой игрок должен находиться на следующем тике.
  • Затем вычислите новый вектор для игрока на стороне сервера, чтобы достичь этой позиции, и используйте это значение для обновления скорости сервера.

Сервер будет обновлять все позиции клиента очень равномерно на следующем тике. Вы можете сгладить изменения направления дальше, просто проецируя 2 или более тиков в будущее. Цель при попытке компенсировать задержку - просто попасть в допустимый предел погрешности.

Другие вопросы по тегам