April 9, 2023 1 minute

Part 2: Linear Interpolation

This guide will show you how you can build a multiplayer experience with Colyseus Multiplayer Framework and Phaser.

In Part 2, we will:

  • Update the player’s positions at every tick
  • Use linear interpolation to smooth player’s movement

Materials

Part 2: Phaser Scene source-code
Part 2: Colyseus Room source-code


Full source-code on GitHub
Live Project on Glitch

1. Why does Part 1 have choppy movement?

On Part 1 of this tutorial, we updated the player’s visual representation at the same instant as state updates are received from the server, using player.onChange():

// listening for server updates
player.onChange(() => {
    // update local position immediately
    entity.x = player.x;
    entity.y = player.y;
});

By doing so, the result is “choppy” because the frequency of updates received from the server is lower than the frequency the client-side render each frame.

Colyseus sends state updates to the client at every 50ms (20fps) by default, whereas the client-side re-renders at every 16.6ms (60fps).

One simple yet effective way to smooth out the movement is to progressively move the player towards the latest position received at every render frame.

2. Applying Linear Interpolation

Cache remote position

The linear interpolation is going to be applied at every render frame.

To allow that, first we need to cache the latest player position received from the server:

// listening for server updates
player.onChange(() => {
    //
    // do not update local position immediately
    // we're going to LERP them during the render loop.
    //
    entity.setData('serverX', player.x);
    entity.setData('serverY', player.y);
});

Interpolate positions at every frame

Now, we are going to iterate over every player entity during our update loop, and use Phaser.Math.Linear() to make the entity slowly move from its current position towards the last position cached previously:

update(time: number, delta: number): void {
    // (...)

    for (let sessionId in this.playerEntities) {
        // interpolate all player entities
        const entity = this.playerEntities[sessionId];
        const { serverX, serverY } = entity.data.values;

        entity.x = Phaser.Math.Linear(entity.x, serverX, 0.2);
        entity.y = Phaser.Math.Linear(entity.y, serverY, 0.2);
    }
}

The third argument of Phaser.Math.Linear is the percentage value. You may want to adjust it for your own needs. It accepts from 0 to 1. The higher it is, the faster the interpolation is going to happen.

Finished 2 of 4:

Part 2: Linear Interpolation

Next:

3. Part 3: Client Predicted Input

Mark all complete and continue

Continue without marking complete