Synchronizing the clients

As the server consistently pushes out updates about the current state of the game world, we need a way for the clients to consume and make use of this data. A simple way to achieve this is to hold the latest server state outside the game class and update itself whenever the data is available since it won't be present every update tick.

// ch4/snake-ch4/share/app.client.js

// All of the requires up top
// …

var serverState = {};

// …

socket.on(gameEvents.client_playerState, function(data){
    otherPlayers = data.filter(function(_player){

        if (_player.id == player.id) {
            serverState = _player;
            return false;
        }

        _player.width = BLOCK_WIDTH;
        _player.height = BLOCK_HEIGHT;
        _player.head.x = parseInt(_player.head.x / BLOCK_WIDTH, 10);
        _player.head.y = parseInt(_player.head.y / BLOCK_HEIGHT, 10);
        _player.pieces = _player.pieces.map(function(piece){
            piece.x = parseInt(piece.x / BLOCK_WIDTH, 10);
            piece.y = parseInt(piece.y / BLOCK_HEIGHT, 10);

            return piece;
        });

        return true;
    });
});

Here, we declare the serverState variable as a module-wide global. Then, we modify the socket listener that grabs the state of all other players when the server updates that, but now, we look for the reference to the player that represents the hero here, and store that in the global serverState variable.

With this global state on hand, we can now check for its existence during the update method of the client and act accordingly. If the state is not there at the beginning of a given update cycle, we update the client as before. If the world state from the server is in fact available to us at the beginning of the next client update tick, we can synchronize the client's positions with the server instead.

// ch4/snake-ch4/share/app.client.js

game.onUpdate = function (delta) {

    if (serverState.id) {
        player.sync(serverState);

        // On subsequent ticks, we may not in sync any more,
        // so let's get rid of the serverState after we use it
        if (player.isSyncd()) {
            serverState = {};
        }
    } else {
        player.update(delta);
        player.checkCollision();

        if (player.head.x < 0) {
            player.head.x = parseInt(renderer.canvas.width / player.width, 10);
        }

        if (player.head.x > parseInt(renderer.canvas.width / player.width, 10)) {
            player.head.x = 0;
        }

        if (player.head.y < 0) {
            player.head.y = parseInt(renderer.canvas.height / player.height, 10);
        }

        if (player.head.y > parseInt(renderer.canvas.height / player.height, 10)) {
            player.head.y = 0;
        }
    }
};

The actual implementation of Player.prototype.sync will depend on our strategy for error correction, which is described in the next couple of sections. Eventually, we'll want to incorporate both teleportation and interpolation, but for now, we'll just check whether any error correction is even necessary.

// ch4/snake-ch4/share/snake.js

var Snake = function (id, x, y, color_hex, width, height) {
    this.id = id;
    this.color = color_hex;
    this.head = {
        x: x,
        y: y
    };
    this.pieces = [this.head];
    this.width = width || 16;
    this.height = height || 16;
    this.readyToGrow = false;
    this.input = {};
    
    this.inSync = true;
};

Snake.prototype.isSyncd = function(){
    return this.inSync;
};

Snake.prototype.sync = function(serverState) {
    var diffX = serverState.head.x - this.head.x;
    var diffY = serverState.head.y - this.head.y;

    if (diffX === 0 && diffY === 0) {
        this.inSync = true;
        return true;
    }

    this.inSync = false;

    // TODO: Implement error correction strategies here
    
    return false;
};

The changes to the snake class are pretty straightforward. We add a flag to let us know whether we still need to synchronize with the server state after a single update cycle. This is necessary because when we decide to interpolate between two points, we'll need multiple update cycles to get there. Next, we add a method that we can call to verify whether the player is (or isn't) in sync with the server, which determines how the snake updated the given frame. Finally, we add a method that performs the actual synchronization. Right now, we simply check whether there is a need to update our position. As we discuss different error correction strategies, we'll update the Snake.prototype.sync method to make use of them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.217.253