how to do client side prediction with server side collision detection?

by SpeekaDievs   Last Updated February 28, 2018 10:13 AM

I recently started working on my first multiplayer game and learning multiple game server mechanics. The game itself uses mouse movement.

For the first version I used a very simple approach, where the physics are run both on the client and the server side, and the client and the server both run at 60fps. The client only renders what it gets from the server and every collision/movement is processed server side.

Now every frame the client sends his mouse position to the server, where the players next position is processed and sent back to the player. This approach worked for some time, but at the time of writing the code, I didn't take the latency in account. For really good internet connections or locally for me, there isn't any issues, but for not so good internet connections there's lag obviosly.

So I started searching for a solution to my problem and I found this article - http://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html

I implemented the Client Side Prediction to my game with the Server reconciliation but now I noticed another problem. Because the collision detection happens server side, I can see that sometimes the collision happens much later than it should. Pretty obvious because the server is in the past.

So I was thinking, what would be the best solution to this problem? I could move the collision detection to the client side, but we all know, that the player can't be trusted, so I would need a way to verify this collision server side. And what if the player tampers with the code and disables collision detection (because some of these collisions are deadly), how could I known when the collision is happening?

Another issue I though of with the client side prediction, is if the player throws a grenade at some other player, but in reality the other player isn't at the specific position yet, because his position was also predicted. What would be the best way to handle this?

You can see my current approach here, which doesn't use the client side prediction at all

The client side of my game uses Phaser, but the server P2.js physics library.

This is how the movement happens without client side prediction

Client Side

let pointer = game.input.activePointer;

//Send a new position data to the server
socket.emit('move-pointer', {
    pointer_x: pointer.x,
    pointer_y: pointer.y,
    pointer_worldx: pointer.worldX,
    pointer_worldy: pointer.worldY,
});

Server Side

//Make a new pointer with the new inputs from the client.
        //contains player positions in server
        let serverPointer = {
            x: data.pointer_x,
            y: data.pointer_y,
            worldX: data.pointer_worldx,
            worldY: data.pointer_worldy
        };

        //moving the player to the new inputs from the player
        if (PositionService.distanceToPointer(movePlayer, serverPointer) <= 30) {
            movePlayer.body.angle = PositionService.moveToPointer(movePlayer, 0, serverPointer, 100);
        } else {
            movePlayer.body.angle = PositionService.moveToPointer(movePlayer, movePlayer.speed, serverPointer);
        }

        let x = movePlayer.body.position[0];
        let y = movePlayer.body.position[1];

        let radius = movePlayer.size + (movePlayer.shield / 2);

        if (x <= (1000 + radius)) {
            movePlayer.body.position[0] = 1000 + radius;
        }

        if (y <= (1000 + radius)) {
            movePlayer.body.position[1] = 1000 + radius;
        }

        if (x >= (game.properties.height - radius)) {
            movePlayer.body.position[0] = game.properties.height - radius;
        }

        if (y >= (game.properties.width - radius)) {
            movePlayer.body.position[1] = game.properties.width - radius;
        }

        movePlayer.x = movePlayer.body.position[0];
        movePlayer.y = movePlayer.body.position[1];

        //new player position to be sent back to client.
        let info = {
            x: movePlayer.body.position[0],
            y: movePlayer.body.position[1],
            angle: movePlayer.body.angle,
            speed: movePlayer.speed,
            ts: data.ts
        };

        //send to sender (not to every clients).
        this.emit('input-received', info);

Client Side, on input-received

        //we're forming a new pointer with the new position
    let newPointer = {
        x: data.x,
        y: data.y,
        worldX: data.x,
        worldY: data.y,
    };

    let distance = PositionService.distanceToPointer(player, newPointer);
    let speed = distance / 0.06;

    player.rotation = PositionService.moveToPointer(player, speed, newPointer);

    if (this.map_group) {
        player.map.x = (player.x / (this.properties.server_width / 220)) - 20;
        player.map.y = (player.y / (this.properties.server_height / 220)) - 20;
    }

PositionService class

class PositionService {

/**
 * @param displayObject
 * @param speed
 * @param pointer
 * @param maxTime
 * @param angle
 * @returns {number}
 */
static moveToPointer(displayObject, speed, pointer, maxTime, angle) {
    if (maxTime === undefined) { maxTime = 0; }

    if(typeof angle === 'undefined'){
        angle = PositionService.angleToPointer(displayObject, pointer);
    }

    if (maxTime > 0) {
        //  We know how many pixels we need to move, but how fast?
        speed = PositionService.distanceToPointer(displayObject, pointer) / (maxTime / 1000);
    }

    displayObject.body.velocity[0] = Math.cos(angle) * speed;
    displayObject.body.velocity[1] = Math.sin(angle) * speed;

    return angle;
}

/**
 * @param displayObject
 * @param pointer
 * @param world
 * @returns {number}
 */
static distanceToPointer (displayObject, pointer, world) {

    if (world === undefined) { world = false; }

    let dx = (world) ? displayObject.world.x - pointer.worldX : displayObject.body.position[0] - pointer.worldX;
    let dy = (world) ? displayObject.world.y - pointer.worldY : displayObject.body.position[1] - pointer.worldY;

    return Math.sqrt(dx * dx + dy * dy);
}

/**
 * @param displayObject
 * @param pointer
 * @param world
 * @returns {number}
 */
static angleToPointer (displayObject, pointer, world) {


    if (world === undefined) { world = false; }

    if (world) {
        return Math.atan2(pointer.worldY - displayObject.world.y, pointer.worldX - displayObject.world.x);
    }
    return Math.atan2(pointer.worldY - displayObject.body.position[1], pointer.worldX - displayObject.body.position[0]);
}
}


Answers 1


It is worth to mention what a client side prediction is. It is technique to perform a prediction of what an authoritative server will compute and respond with. Therefore this prediction should include, based on business or gameplay requirements, any logic that server performs but client wants to predict it to mimic no or low latency overhead. That is used to locally (client side) show more "present" state of the game - if prediction is correct enough, player will not even notice it while not feeling actual latency; if prediction is wrong (the higher lag, the higher chance of wrong prediction) then player will see these explicit snapping of position, etc.

In your case, exactly as DMGregory said in his comment, you should consider collision logic on the client side as well as it is your core gameplay mechanics that highly impacts the end user experience.

When it comes to your grenade example, it is very wise question and there is no "one size fits all" solution to this and different games implement it differently depending on high level aspects like genere or even low level aspects like what weapon player used in this case. Check out this guide which Valve provides for Source Engine. There is described server side lag compensation mechanism that is used to favor players and what they see in their client applications.

In the end, it is up to game developer to decide what is important to be included in client/server side lag compensation solutions and it often will be a trade-off of user experience and solution complexity.

Dawid Komorowski
Dawid Komorowski
February 28, 2018 17:39 PM

Related Questions


How does client-side prediction work?

Updated May 23, 2017 11:13 AM




Multiplayer game server sync

Updated June 12, 2016 08:05 AM