我正在开发一个浏览器多人游戏,其中每个客户端插入(线性)服务器发送的实体帧。在高帧速率(> 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);
};
如上所述,这会在运动中产生抖动和跳跃。有什么我做错了吗?我怎么能让这个动作顺利?
答案 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