实时多人游戏延迟补偿
Real-time multiplayer game latency compensation
简而言之,我正在开发一款实时多人游戏。在我的游戏中,客户端以 20Hz 的频率向服务器发送更新的位置和速度数据。在下面的示例代码中,我使用 LuaJIT FFI 将数据从 Lua table 转换为 C 结构。这是通过网络传输数据的绝妙方式:
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
所以问题就在这里:对象非常紧张。每次我收到一个数据包时,其他玩家都会向前或向后扭曲一点,所以我的延迟补偿似乎关闭了,这使得我的插值关闭了。也许有人可以指出我的代码中的一些明显缺陷,或者我对流程工作原理的理解?
为了流畅的动画,您的服务器端位置更新应该使用为您的 position/velocity 向量存储的当前值在固定时钟上进行。
收到客户端更新后,您需要为下一个订单号进行两次计算:
- 首先,使用客户端的向量,找到玩家在下一个 tick 应该处于的位置。
- 接下来,计算服务器端玩家到达该位置的新向量,并使用该值更新服务器速度。
然后服务器将在下一个订单号上以非常统一的方式更新所有客户端位置。您可以通过简单地向未来投射 2 个或更多个刻度来进一步平滑方向变化。尝试补偿延迟的目标实际上只是落在可接受的误差范围内。
简而言之,我正在开发一款实时多人游戏。在我的游戏中,客户端以 20Hz 的频率向服务器发送更新的位置和速度数据。在下面的示例代码中,我使用 LuaJIT FFI 将数据从 Lua table 转换为 C 结构。这是通过网络传输数据的绝妙方式:
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
所以问题就在这里:对象非常紧张。每次我收到一个数据包时,其他玩家都会向前或向后扭曲一点,所以我的延迟补偿似乎关闭了,这使得我的插值关闭了。也许有人可以指出我的代码中的一些明显缺陷,或者我对流程工作原理的理解?
为了流畅的动画,您的服务器端位置更新应该使用为您的 position/velocity 向量存储的当前值在固定时钟上进行。
收到客户端更新后,您需要为下一个订单号进行两次计算:
- 首先,使用客户端的向量,找到玩家在下一个 tick 应该处于的位置。
- 接下来,计算服务器端玩家到达该位置的新向量,并使用该值更新服务器速度。
然后服务器将在下一个订单号上以非常统一的方式更新所有客户端位置。您可以通过简单地向未来投射 2 个或更多个刻度来进一步平滑方向变化。尝试补偿延迟的目标实际上只是落在可接受的误差范围内。