多人浏览器游戏:线性插值导致抖动和跳跃

Multiplayer browser game: linear interpolation causing jittering and jumping

我正在开发一款浏览器多人游戏,其中每个客户端都会插入(线性)服务器发送的实体帧。它在高帧率(> 30fps)下看起来并不太糟糕,但在较低的帧率(<30fps)下开始抖动并冻结和跳跃以及非常低的帧率(<10fps)。我想降低帧率,我知道这是可能的(请参阅 Brutal.io,它以 10fps 发送更新)。

这是我使用的基本算法:

此片段不包含具体代码,但应该足以演示基本概述(有关信息,请参阅代码中的注释):

var serverDelta = 1; // Setting up a variable to store the time between server updates

// Called when the server sends an update (aiming for 10fps)
function onServerUpdate(message) {
    serverDelta = Date.now() - lastServerFrame;
}

// Called when the client renders (could be as high as 60fps)
var onClientRender() {
    var clientDelta = Date.now() - lastUpdateFrame;

    // Describes the multiplier used for the linear interpolation function
    var lerpMult = clientDelta / serverDelta;
    if (lerpMult > 1) { // Making sure that the screen position doesn't go beyond the server position
        lerpMult = 1;
    }
    lastUpdateFrame = Date.now();

    ...

    // For each entity
    // ($x,$y) is position sent by server, (x,y) is current position on screen
    entity.x = linearInterpolate(entity.x, entity.$x, lerpMult / 2);
    entity.y = linearInterpolate(entity.y, entity.$y, lerpMult / 2);
}

function linearInterpolate(a, b, f) {
    return (a * (1 - f)) + (b * f);
};

如上所述,这会在运动中产生抖动和跳跃。我做错了什么吗?我怎样才能使这个动作流畅?

插值必须在两个服务器状态之间。您可以保留在客户端上接收到的最后一个 X 服务器状态的历史记录。每个服务器状态代表一个特定的帧。

例如,假设您的客户端保留了以下服务器状态及其帧:

state[0] = {frame: 0, ... };
state[1] = {frame: 10, ... };
state[2] = {frame: 20, ... };

如果客户端现在正在渲染第 15 帧,则它必须在 state[1]state[2] 之间的中间位置进行插值。公式是

// prev=1, next=2 
let interpolatePercent = (clientFrame - serverState[prev].frame) / serverUpdateRate;
entity.x = interpolatePercent * (serverState[next].entity.x - serverState[prev].entity.x) + serverState[prev].entity.x;

在您的代码中,lerpMult 很可能大于 1,在这种情况下,您现在进行的是外推而不是内插,这要困难得多。

您可能还想看看为浏览器多人游戏实现插值(和外插)的开源库:https://github.com/lance-gg/lance