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

3
我正在开发一款多人在线浏览器游戏,其中每个客户端使用线性插值来处理服务器发送的实体帧。当帧率较高(> 30fps)时,效果还不错,但当帧率较低时(< 30fps),会出现抖动,并且在非常低的帧率下(< 10fps)会出现冻结和跳跃。我想降低帧率,我知道这是可能的(参见Brutal.io 以10fps发送更新)。
以下是我正在使用的基本算法: - 服务器以帧速率(例如10fps)发送更新。 - 客户端以帧速率(例如60fps)呈现游戏。 - 客户端不直接更新屏幕上的实体以匹配服务器的数据:这看起来很抖动。 - 相反,它使用线性插值函数来平滑处理服务器更新之间的帧。 - 在10:60fps的例子中,客户端将呈现6个帧以创建平滑的动画。 - 它通过测量服务器更新之间以及客户端渲染帧之间的差异来实现这一点。 - 然后,它通过将客户端delta除以服务器delta来获取乘数。 - 接着,它调用线性插值函数,使用屏幕位置、服务器位置和乘数来生成新的屏幕位置。
此片段不包含特定代码,但应该足以演示基本概述(请参见代码中的注释)。
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);
};

如上所述,这会导致运动中的抖动和跳跃。我是否做错了什么?如何使此运动平滑?

我正在尝试理解 - 在插值中,如何在发生之前知道最终服务器帧是什么?换句话说,如何在不引起延迟的情况下在一个值和另一个值之间进行插值? - clabe45
嗯,好的,你是说我现在的做法是使用前一帧与当前值之间的差值进行插值吗?那解决方法是存储“上一个服务器差值”并使用它吗? - Luke
@clabe45 抱歉,忘记提到你了。请看上面 ^^^。 - Luke
@clabe45,我刚试了一下:它使得运行更加平滑,但实体仍然每隔几秒钟就会跳动。 - Luke
但是执行“最后服务器增量”不会导致显著的延迟吗? - clabe45
@clabe45,也许会有用。有什么想法吗? - Luke
1个回答

2

插值必须在两个服务器状态之间进行。您可以在客户端上保留最近收到的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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接