Predicting the future with a local game server

The strategy that we will use in order to make our clients responsive, yet bound to the authoritative server, is that we will act on the input that we receive from the player while we tell the server about the input. In other words, we will need to take the player's input and predict what will happen to our game state as a result, while we wait to hear back from the server with the actual output of the player's action.

Client-side prediction can be summarized as your best guess about what should happen between authoritative updates. In other words, we can reuse some of the server code that updates the game world on the client-side so that our guess about what the output should be from the player's input is pretty much the same as what the server will simulate.

Reporting user input

The first thing that we'll change is the control mechanism on the client side. Instead of simply keeping track of our position locally, we'll also inform the server that the player has pressed a key.

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

document.body.addEventListener('keydown', function (e) {
    var key = e.keyCode;

    switch (key) {
        case keys.ESC:
            game.stop();
            break;

        case keys.SPACEBAR:
            game.start();
            break;

        case keys.LEFT:
        case keys.RIGHT:
        case keys.UP:
        case keys.DOWN:
            player.setKey(key);
            socket.emit(gameEvents.server_setPlayerKey, {
                    roomId: roomId,
                    playerId: player.id,
                    keyCode: key
                }
            );

            break;
    }
});

Of course, doing this directly in the event handler's callback might quickly overwhelm the server, so be sure to time this upward reporting. One way to do this is to use the tick update to contact the server.

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

game.onUpdate = function (delta) {
    player.update(delta);
    player.checkCollision();

    // …

    socket.emit(gameEvents.server_setPlayerKey, {
            roomId: roomId,
            playerId: player.id,
            keyState: player.input
        }
    );
};

Now, we update the server at the same frequency that we update our local simulation, which is not a bad idea. However, you might also consider leaving all networking logic outside of the game class (update and render methods) so that the networking aspects of the game is abstracted out of the game altogether.

For this, we can put the socket emitter right back in the controller's event handler; however, instead of calling the server right away, we can use a timer to keep the updates consistent. The idea is that, when a key is pressed, we call the server right away with the update. If the user pushes a key again before some time has gone by, we wait a certain amount of time before calling the server again.

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

// All of the requires up top
// …

var inputTimer = 0;
var inputTimeoutPeriod = 100;

// …

document.body.addEventListener('keydown', function (e) {
    var key = e.keyCode;

    switch (key) {
        case keys.ESC:
            game.stop();
            break;

        case keys.SPACEBAR:
            game.start();
            break;

        case keys.LEFT:
        case keys.RIGHT:
        case keys.UP:
        case keys.DOWN:
            player.setKey(key);

            if (inputTimer === 0) {
                inputTimer = setTimeout(function(){
                    socket.emit(gameEvents.server_setPlayerKey, {
                            roomId: roomId,
                            playerId: player.id,
                            keyCode: key
                        }
                    );
                }, inputTimeoutPeriod);
            } else {
                clearTimeout(inputTimer);
                inputTimer = 0;
            }

            break;
    }
});

Here, the inputTimer variable is a reference to the timer that we created with setTimeout, which we can be canceled at any moment until the timer is actually fired. This way, if the player presses many keys really fast (or holds a key down for a while), we can ignore the additional events.

One side effect of this implementation is that, if the player holds down the same key for a long time, the timer that wraps the call to socket.emit will continue to be canceled, and the server will never be notified of subsequent key presses. While this may seem like a potential problem at first, it is actually a very welcome feature. Firstly, in the case of this particular game where pressing the same key two or more times has no effect, we really don't need to report the additional presses to the server. Secondly (and this holds true for any other type of game as well), we can let the server assume that, after the player presses the right arrow key, the right key is still being pressed until we tell the server otherwise. Since our Snake game doesn't have a concept of a key being released (meaning that the snake will constantly move in the direction of the last key press until we change its direction), the server will continue to move the snake in a given direction until we press a different key and tell the server to move it in the new direction.

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

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