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

时间:2017-08-10 00:31:21

标签: javascript interpolation multiplayer motion linear-interpolation

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

这是我正在使用的基本算法:

  • 服务器以帧速率(例如,10fps)
  • 发送更新
  • 客户端以帧速率(例如,60fps)
  • 渲染游戏
  • 客户端不会更新屏幕上的实体以直接匹配服务器的数据:这看起来很紧张
  • 相反,它使用线性插值函数来平滑服务器更新之间的帧
  • 以10:60fps为例,客户端将在其间渲染6帧以创建平滑动画
  • 它通过测量服务器更新之间的差异(差异)以及客户端渲染帧
  • 来实现
  • 然后通过将客户端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);
};

如上所述,这会在运动中产生抖动和跳跃。有什么我做错了吗?我怎么能让这个动作顺利?

1 个答案:

答案 0 :(得分: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