0
\$\begingroup\$

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]);
}
}
\$\endgroup\$
1
  • \$\begingroup\$ I'm always a bit confused when folks ask, "could I do calculation X on the client? But the client can't be trusted!" I don't see a contradiction here. Do the calculation on the client, AND don't trust it. Just use it locally for visual plausibility, without ever sending it to the server or using it for authoritative gameplay calcs, and let the server come to its own authoritative conclusion which it will transmit to you as soon as it can. There's nothing wrong with filling in the gaps client-side, and if your client isn't breaking the rules, it generally shouldn't see large corrections. \$\endgroup\$
    – DMGregory
    Feb 28, 2018 at 12:37

1 Answer 1

0
\$\begingroup\$

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.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .