I'm developing a browser multiplayer game, in which each client interpolates (linear) the frames of entities sent by the server. It doesn't look too bad at a high framerate (> 30fps), but starts to jitter at lower framerates (< 30fps) and freezes and jumps and very low framerates (< 10fps). I want to reduce the framerate, and I know it's possible (see Brutal.io which sends updates at 10fps).
This is the basic algorithm which I'm using:
This snippet doesn't contain specific code, but should be good enough to demonstrate the basic overview (see comments in code for info):
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);
};
As stated above, this creates jitter and jumping in the motion. Is there something I'm doing wrong? How would I make this motion smooth?
Interpolation must be between two server states. You can keep a history of the last X server states received on the client. Each server state represents a specific frame.
For example, let's say your client kept the following server states and their frames:
state[0] = {frame: 0, ... };
state[1] = {frame: 10, ... };
state[2] = {frame: 20, ... };
If the client is now rendering frame 15, then it must interpolate the positions half-way between state[1]
and state[2]
. The formula is
// 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;
In your code, lerpMult
may well be larger than 1, in which case you are now doing extrapolation instead of interpolation which is much harder.
Also you may also want to take a look at the open source library which implements interpolation (and extrapolation) for browser multiplayer games: https://github.com/lance-gg/lance