Welcome!

Join our community of MMO enthusiasts and game developers! By registering, you'll gain access to discussions on the latest developments in MMO server files and collaborate with like-minded individuals. Join us today and unlock the potential of MMO server development!

Join Today!

6.) MMO From Scratch - Players Continued

Joined
Jun 8, 2007
Messages
1,985
Reaction score
490
Previous (Players)
Table of Contents (Introduction)
Next (Players 3)



Now let's work on the client side of players.

First, let's get a single player moving around the world, and then we'll work on other players.

Before we do anything with the players, we need to tell the server that this client is ready. The client is ready once the canvas is created and all of the game object's have run their create methods.

In /public/js/main.js, we need to emit a 'game-ready' event to the server.
So let's change that code to this (change in bold):
Code:
    [COLOR=#008000][B]function[/B][/COLOR] create() {
        plugins.forEach([COLOR=#008000][B]function[/B][/COLOR] (plugin) {
            plugin.create();
        });
        [B]comms.emit([COLOR=#BA2121]'game-ready'[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);[/B]
    }

So, we need a file: /public/js/objects/player.js

Let's give ourselves a checklist of what needs to be done.
  • Read user-input from user.
  • Get initial data from server about this player's game-state.
  • Ability to send server commands based on user-input.
  • Preload spritesheet for player.
  • Create player sprite, prepare animations, and put user's username above their head.
  • Based on user input, move the player around the world.
  • Correct user's position based on server response.
  • Use lag-compensation to make animations and position corrections appear smooth.

We're going to end up with 2 different perspectives of where the user is in the world. We have the most recently received server's perspective, and the sprite's perspective. We're going to use the client's step in relation to the server's step to make the movement appear fairly smooth.

We'll create a helper function to read the keyboard's state, so we can (in the update tick) check if a key is being held down. I chose to use WASD as well as the arrow (cursor) keys.

Code:
    [COLOR=#008000][B]function[/B][/COLOR] Keys(game) {
        [COLOR=#008000][B]var[/B][/COLOR] keyList [COLOR=#666666]=[/COLOR] {
            up[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.W),
            down[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.S),
            left[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.A),
            right[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.D),
            space[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR)
        };
        [COLOR=#008000][B]var[/B][/COLOR] cursors [COLOR=#666666]=[/COLOR] game.input.keyboard.createCursorKeys();
        [COLOR=#008000][B]this[/B][/COLOR].isDown [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] isDown(key) {
            [COLOR=#008000][B]if[/B][/COLOR] (key [COLOR=#008000][B]in[/B][/COLOR] keyList [COLOR=#666666]&&[/COLOR] keyList[key].isDown) {
                [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]true[/B][/COLOR];
            }
            [COLOR=#008000][B]if[/B][/COLOR] (key [COLOR=#008000][B]in[/B][/COLOR] cursors [COLOR=#666666]&&[/COLOR] cursors[key].isDown) {
                [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]true[/B][/COLOR];
            }
            [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]false[/B][/COLOR];
        };
    }


We'll initialize this key-state machine in the Player.create method. Before we can even worry about keystrokes, we need to preload the spritesheet, and create the player sprite. We should also create the text that appears the sprite's head in the Player.create method. The player should be facing a direction when they are spawned. Rather than have the server be aware of which direction the player is facing, we'll just let each client figure that out, and spawn players facing the screen every time the game is first initialized.

Code:
    self.preload [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        main.game.load.spritesheet([COLOR=#BA2121]'player'[/COLOR], [COLOR=#BA2121]'/assets/game/dude_sprite.png'[/COLOR], [COLOR=#666666]25[/COLOR], [COLOR=#666666]50[/COLOR]);
    };
    self.create [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        [COLOR=#408080][I]// Set up Player[/I][/COLOR]
        self.player [COLOR=#666666]=[/COLOR] main.game.add.sprite([COLOR=#666666]250[/COLOR], [COLOR=#666666]250[/COLOR], [COLOR=#BA2121]'player'[/COLOR]);
        self.player.anchor.setTo(.[COLOR=#666666]5[/COLOR], .[COLOR=#666666]9[/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'down'[/COLOR], [[COLOR=#666666]0[/COLOR], [COLOR=#666666]1[/COLOR], [COLOR=#666666]0[/COLOR], [COLOR=#666666]2[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'left'[/COLOR], [[COLOR=#666666]3[/COLOR], [COLOR=#666666]4[/COLOR], [COLOR=#666666]3[/COLOR], [COLOR=#666666]5[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'right'[/COLOR], [[COLOR=#666666]3[/COLOR], [COLOR=#666666]4[/COLOR], [COLOR=#666666]3[/COLOR], [COLOR=#666666]5[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'up'[/COLOR], [[COLOR=#666666]6[/COLOR], [COLOR=#666666]7[/COLOR], [COLOR=#666666]6[/COLOR], [COLOR=#666666]8[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);

        [COLOR=#408080][I]// Set the still-frame- the direction the player should be standing- to facing the screen.[/I][/COLOR]
        self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];

        [COLOR=#408080][I]// Set up text above player[/I][/COLOR]
        self.text [COLOR=#666666]=[/COLOR] main.game.add.text([COLOR=#666666]250[/COLOR], [COLOR=#666666]190[/COLOR], [COLOR=#BA2121]'Loading..'[/COLOR]);
        self.text.anchor.set([COLOR=#666666]0.5[/COLOR]);
        self.text.align [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'center'[/COLOR];
        self.text.font [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'Arial Black'[/COLOR];
        self.text.fontSize [COLOR=#666666]=[/COLOR] [COLOR=#666666]16[/COLOR];
        self.text.stroke [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'#000000'[/COLOR];
        self.text.strokeThickness [COLOR=#666666]=[/COLOR] [COLOR=#666666]3[/COLOR];
        self.text.fill [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'#FFFFFF'[/COLOR];

        [COLOR=#408080][I]// Set up listeners for keyboard input[/I][/COLOR]
        self.key [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]new[/B][/COLOR] Keys(main.game);

        [COLOR=#408080][I]// Make camera follow this player[/I][/COLOR]
        main.game.camera.follow(self.player);
    };


Please refer to the Phaser.io documentation for questions regarding sprites/text/camera. Basically, we create a player sprite, we set the anchor to the center of the player, somewhere near their feet (this is important later to sort visible objects on the screen properly, and for collision detection). We then add an animation for walking in all 4 directions.

We set the stillFrame to 0- that is the graphic standing still, facing the client's screen, looking right at the human player.

We then set up the text. We set the text to say 'Loading..' until the server responds with the username. Remember, we have no data from the server yet of where the player should actually be in the world, or what their name is, or what they are wearing or anything. We set the anchor for the text, so we can center it over the player more easily. We set alignment to center, the style of font, the size, the stroke color, the stroke thickness, and the fill color of the text should all be fairly obvious.

We initialize the key-state machine, and we tell the game.camera to follow the player we just created.

Now let's think of what a server command should do.. It's actually really simple, we can make a function that takes a command, and then tells the server the step, time, and action we want it to take. We need a private 'step' variable, we can make that in the Player scope.. Just a regular variable.. 3 lines should do the work for us.

Code:
    [COLOR=#008000][B]function[/B][/COLOR] serverCommand(command) {
        step [COLOR=#666666]+=[/COLOR] [COLOR=#666666]1[/COLOR];
        [COLOR=#008000][B]var[/B][/COLOR] data [COLOR=#666666]=[/COLOR] {time[COLOR=#666666]:[/COLOR] [COLOR=#008000]Date[/COLOR].now(), action[COLOR=#666666]:[/COLOR] command, step[COLOR=#666666]:[/COLOR] step};
        comms.emit([COLOR=#BA2121]'player-input'[/COLOR], data);
    }


So, in the update function we're going to have to do nothing until we get the player's data. Once we do get the player's data, we're going to have to update the position of that player. Since we're going to use tweens to move the player around, we'll have to have a newPos object to store the changes, and use a tween to move the player to that new position later. We're also going to need the current time, so we'll make a variable named 'now' for that information.
Code:
    self.update [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        [COLOR=#008000][B]var[/B][/COLOR] now [COLOR=#666666]=[/COLOR] [COLOR=#008000]Date[/COLOR].now();
        [COLOR=#008000][B]var[/B][/COLOR] newPos [COLOR=#666666]=[/COLOR] {x[COLOR=#666666]:[/COLOR] self.player.x, y[COLOR=#666666]:[/COLOR] self.player.y};
        [COLOR=#008000][B]if[/B][/COLOR] (self.playerData [COLOR=#666666]===[/COLOR] [COLOR=#008000][B]void[/B][/COLOR] [COLOR=#666666]0[/COLOR]) {
            [COLOR=#008000][B]return[/B][/COLOR];
        }
        [COLOR=#008000][B]if[/B][/COLOR] (self.playerData.username [COLOR=#666666]!==[/COLOR] self.text.text) {
            self.text.text [COLOR=#666666]=[/COLOR] self.playerData.username;
            newPos [COLOR=#666666]=[/COLOR] self.playerData.game;
            main.game.camera.focusOnXY(self.playerData.game.x, self.playerData.game.y);
        }

We're checking if the playerData username is different then the text above the player's head. The only time this should be different is the first time we run this code after getting the data from the server. Or if a cheater changes it for some reason. In any case, it's a good time to set the basic things up. We set the text to the username, set up a newPos, and focus the game camera on the player's position from the server.

Now, we're going to need a lastMove and a moveTime- as we're going to be moving the player ever 100ms. Those need to be scoped to the Player function, not the update method. Also, we don't want to increment the step variable forever- that's a memory leak waiting to happen. So we'll set the step variable to 0 anytime the serverStep matches the client's step. That's also a good time to force the client's sprite position to the server's position. Since we're in development, we should probably log the steps on the client and server, just so we know that our code is working properly.

Code:
        [COLOR=#008000][B]if[/B][/COLOR] (lastMove [COLOR=#666666]+[/COLOR] moveTime [COLOR=#666666]<[/COLOR] now) {
            lastMove [COLOR=#666666]=[/COLOR] now;
            [COLOR=#008000][B]if[/B][/COLOR] (step [COLOR=#666666]===[/COLOR] serverStep) {
                step [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];
                newPos [COLOR=#666666]=[/COLOR] self.playerData.game;
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (step [COLOR=#666666]!==[/COLOR] [COLOR=#666666]0[/COLOR]) {
                console.log([COLOR=#BA2121]"steps:"[/COLOR], step, serverStep);
            }
            //TODO: Move player to newPos.
        }


Our sprite only has one sideways direction, if you look at it. And it is moving left. If we want to move the sprite to the right, we simply flip the sprite on the y axis. In Phaser that's easy- especially since we set the anchor on the x-axis to 0.5 (center). We also want to play an animation anytime a sprite is walking. Finally, we want to set the stillFrame to the direction the player is walking now. That way when they stop walking, they will be facing the same direction.

Code:
            [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'left'[/COLOR])) {
                [COLOR=#408080][I]// Move to left[/I][/COLOR]
                [COLOR=#008000][B]if[/B][/COLOR] (self.player.scale.x [COLOR=#666666]<[/COLOR] [COLOR=#666666]0[/COLOR]) {
                    self.player.scale.x [COLOR=#666666]*=[/COLOR] [COLOR=#666666]-1[/COLOR];
                }
                newPos.x [COLOR=#666666]-=[/COLOR] horrSpeed;
                serverCommand([COLOR=#BA2121]'left'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'left'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]3[/COLOR];
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'right'[/COLOR])) {
                [COLOR=#408080][I]// Move to right[/I][/COLOR]
                [COLOR=#008000][B]if[/B][/COLOR] (self.player.scale.x [COLOR=#666666]>[/COLOR] [COLOR=#666666]0[/COLOR]) {
                    self.player.scale.x [COLOR=#666666]*=[/COLOR] [COLOR=#666666]-1[/COLOR];
                }
                newPos.x [COLOR=#666666]+=[/COLOR] horrSpeed;
                serverCommand([COLOR=#BA2121]'right'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'right'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]3[/COLOR];
            }


Moving up/down is even easier- because we don't have to worry about flipping the sprite to change directions. Other than that, it's the same.

Code:
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'up'[/COLOR])) {
                [COLOR=#408080][I]// Move up[/I][/COLOR]
                newPos.y [COLOR=#666666]-=[/COLOR] vertSpeed;
                serverCommand([COLOR=#BA2121]'up'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'up'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]6[/COLOR];
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'down'[/COLOR])) {
                [COLOR=#408080][I]// Move down[/I][/COLOR]
                newPos.y [COLOR=#666666]+=[/COLOR] vertSpeed;
                serverCommand([COLOR=#BA2121]'down'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'down'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];
            }


Finally, if none of those keys are down, we need to stop the animation and set the sprite's frame to the still frame. After all that is done, we'll run the tweens for the player's sprite and the text above the sprite's head.

Code:
            } [COLOR=#008000][B]else[/B][/COLOR] {
                [COLOR=#408080][I]// Player not moving[/I][/COLOR]
                self.player.animations.stop();
                self.player.frame [COLOR=#666666]=[/COLOR] self.stillFrame;
            }
            main.game.add.tween(self.player).
                to(newPos, moveTime [COLOR=#666666]*[/COLOR] tweenTime, [COLOR=#BA2121]'Linear'[/COLOR]).
                start();
            main.game.add.tween(self.text).
                to({
                    x[COLOR=#666666]:[/COLOR] newPos.x [COLOR=#666666]-[/COLOR] [COLOR=#666666]2[/COLOR], 
                    y[COLOR=#666666]:[/COLOR] newPos.y [COLOR=#666666]-[/COLOR] [COLOR=#666666]60[/COLOR]
                }, moveTime [COLOR=#666666]*[/COLOR] tweenTime, [COLOR=#BA2121]'Linear'[/COLOR]).
                start();


We need to make the tweens a little bit shorter than the moveTime, so we set tweenTime to a decimal between 0 and 1. The higher it is, the more smooth the animations, but the more jerky and noticeable it is when the authoritative server steps in to make a change. Right now I'm trying .9- it seems to work well, but we'll see what happens when we add artificial lag.

After the Player.update() code, we should put some communication listeners. The initial player channel the server sends us simply called 'player', and all mid-game updates are sent via 'player-move'.

The first one should just assign the data from the server to self.playerData.
The second one should do more/less the same thing, but we only want to change the game data. We're not doing anything with the lag yet, but it's useful to log this so we know it's happening. Also in the second one, we need to update the serverStep variable.

Code:
    comms.on([COLOR=#BA2121]'player'[/COLOR], [COLOR=#008000][B]function[/B][/COLOR] (data) {
        self.playerData [COLOR=#666666]=[/COLOR] data;
    });
    comms.on([COLOR=#BA2121]'player-move'[/COLOR], [COLOR=#008000][B]function[/B][/COLOR] (data) {
        console.log([COLOR=#BA2121]'player-move:'[/COLOR], data.game);
        [COLOR=#008000][B]var[/B][/COLOR] lag [COLOR=#666666]=[/COLOR] [COLOR=#008000]Date[/COLOR].now() [COLOR=#666666]-[/COLOR] data.time;
        self.playerData.game [COLOR=#666666]=[/COLOR] data.game;
        console.log([COLOR=#BA2121]"lag:"[/COLOR], lag);
        serverStep [COLOR=#666666]=[/COLOR] data.step;
    });


And that's it. Here is the entire /public/js/objects/player.js file:
Code:
[COLOR=#008000][B]function[/B][/COLOR] Player (main) {
    [COLOR=#BA2121]"use strict"[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] self [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]this[/B][/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] lastMove [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] moveTime [COLOR=#666666]=[/COLOR] [COLOR=#666666]100[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] horrSpeed [COLOR=#666666]=[/COLOR] [COLOR=#666666]7.5[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] vertSpeed [COLOR=#666666]=[/COLOR] [COLOR=#666666]7.5[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] step [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] serverStep [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] tweenTime [COLOR=#666666]=[/COLOR] .[COLOR=#666666]9[/COLOR];
    self.playerData;
    [COLOR=#008000][B]function[/B][/COLOR] Keys(game) {
        [COLOR=#008000][B]var[/B][/COLOR] keyList [COLOR=#666666]=[/COLOR] {
            up[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.W),
            down[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.S),
            left[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.A),
            right[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.D),
            space[COLOR=#666666]:[/COLOR] game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR)
        };
        [COLOR=#008000][B]var[/B][/COLOR] cursors [COLOR=#666666]=[/COLOR] game.input.keyboard.createCursorKeys();
        [COLOR=#008000][B]this[/B][/COLOR].isDown [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] isDown(key) {
            [COLOR=#008000][B]if[/B][/COLOR] (key [COLOR=#008000][B]in[/B][/COLOR] keyList [COLOR=#666666]&&[/COLOR] keyList[key].isDown) {
                [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]true[/B][/COLOR];
            }
            [COLOR=#008000][B]if[/B][/COLOR] (key [COLOR=#008000][B]in[/B][/COLOR] cursors [COLOR=#666666]&&[/COLOR] cursors[key].isDown) {
                [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]true[/B][/COLOR];
            }
            [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]false[/B][/COLOR];
        };
    }
    [COLOR=#008000][B]function[/B][/COLOR] serverCommand(command) {
        step [COLOR=#666666]+=[/COLOR] [COLOR=#666666]1[/COLOR];
        [COLOR=#008000][B]var[/B][/COLOR] data [COLOR=#666666]=[/COLOR] {time[COLOR=#666666]:[/COLOR] [COLOR=#008000]Date[/COLOR].now(), action[COLOR=#666666]:[/COLOR] command, step[COLOR=#666666]:[/COLOR] step};
        comms.emit([COLOR=#BA2121]'player-input'[/COLOR], data);
    }
    self.preload [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        main.game.load.spritesheet([COLOR=#BA2121]'player'[/COLOR], [COLOR=#BA2121]'/assets/game/dude_sprite.png'[/COLOR], [COLOR=#666666]25[/COLOR], [COLOR=#666666]50[/COLOR]);
    };
    self.create [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        [COLOR=#408080][I]// Set up Player[/I][/COLOR]
        self.player [COLOR=#666666]=[/COLOR] main.game.add.sprite([COLOR=#666666]250[/COLOR], [COLOR=#666666]250[/COLOR], [COLOR=#BA2121]'player'[/COLOR]);
        self.player.anchor.setTo(.[COLOR=#666666]5[/COLOR], .[COLOR=#666666]9[/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'down'[/COLOR], [[COLOR=#666666]0[/COLOR], [COLOR=#666666]1[/COLOR], [COLOR=#666666]0[/COLOR], [COLOR=#666666]2[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'left'[/COLOR], [[COLOR=#666666]3[/COLOR], [COLOR=#666666]4[/COLOR], [COLOR=#666666]3[/COLOR], [COLOR=#666666]5[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'right'[/COLOR], [[COLOR=#666666]3[/COLOR], [COLOR=#666666]4[/COLOR], [COLOR=#666666]3[/COLOR], [COLOR=#666666]5[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);
        self.player.animations.add([COLOR=#BA2121]'up'[/COLOR], [[COLOR=#666666]6[/COLOR], [COLOR=#666666]7[/COLOR], [COLOR=#666666]6[/COLOR], [COLOR=#666666]8[/COLOR]], [COLOR=#666666]10[/COLOR], [COLOR=#008000][B]true[/B][/COLOR]);

        [COLOR=#408080][I]// Set the still-frame- the direction the player should be standing- to facing the screen.[/I][/COLOR]
        self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];

        [COLOR=#408080][I]// Set up text above player[/I][/COLOR]
        self.text [COLOR=#666666]=[/COLOR] main.game.add.text([COLOR=#666666]250[/COLOR], [COLOR=#666666]190[/COLOR], [COLOR=#BA2121]'Loading..'[/COLOR]);
        self.text.anchor.set([COLOR=#666666]0.5[/COLOR]);
        self.text.align [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'center'[/COLOR];
        self.text.font [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'Arial Black'[/COLOR];
        self.text.fontSize [COLOR=#666666]=[/COLOR] [COLOR=#666666]16[/COLOR];
        self.text.stroke [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'#000000'[/COLOR];
        self.text.strokeThickness [COLOR=#666666]=[/COLOR] [COLOR=#666666]3[/COLOR];
        self.text.fill [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]'#FFFFFF'[/COLOR];

        [COLOR=#408080][I]// Set up listeners for keyboard input[/I][/COLOR]
        self.key [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]new[/B][/COLOR] Keys(main.game);

        [COLOR=#408080][I]// Make camera follow this player[/I][/COLOR]
        main.game.camera.follow(self.player);
    };
    self.update [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        [COLOR=#008000][B]var[/B][/COLOR] now [COLOR=#666666]=[/COLOR] [COLOR=#008000]Date[/COLOR].now();
        [COLOR=#008000][B]var[/B][/COLOR] newPos [COLOR=#666666]=[/COLOR] {x[COLOR=#666666]:[/COLOR] self.player.x, y[COLOR=#666666]:[/COLOR] self.player.y};
        [COLOR=#008000][B]if[/B][/COLOR] (self.playerData [COLOR=#666666]===[/COLOR] [COLOR=#008000][B]void[/B][/COLOR] [COLOR=#666666]0[/COLOR]) {
            [COLOR=#008000][B]return[/B][/COLOR];
        }
        [COLOR=#008000][B]if[/B][/COLOR] (self.playerData.username [COLOR=#666666]!==[/COLOR] self.text.text) {
            self.text.text [COLOR=#666666]=[/COLOR] self.playerData.username;
            newPos [COLOR=#666666]=[/COLOR] self.playerData.game;
            main.game.camera.focusOnXY(self.playerData.game.x, self.playerData.game.y);
        }
        [COLOR=#008000][B]if[/B][/COLOR] (lastMove [COLOR=#666666]+[/COLOR] moveTime [COLOR=#666666]<[/COLOR] now) {
            lastMove [COLOR=#666666]=[/COLOR] now;
            [COLOR=#008000][B]if[/B][/COLOR] (step [COLOR=#666666]===[/COLOR] serverStep) {
                step [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];
                newPos [COLOR=#666666]=[/COLOR] self.playerData.game;
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (step [COLOR=#666666]!==[/COLOR] [COLOR=#666666]0[/COLOR]) {
                console.log([COLOR=#BA2121]"steps:"[/COLOR], step, serverStep);
            }
            [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'left'[/COLOR])) {
                [COLOR=#408080][I]// Move to left[/I][/COLOR]
                [COLOR=#008000][B]if[/B][/COLOR] (self.player.scale.x [COLOR=#666666]<[/COLOR] [COLOR=#666666]0[/COLOR]) {
                    self.player.scale.x [COLOR=#666666]*=[/COLOR] [COLOR=#666666]-1[/COLOR];
                }
                newPos.x [COLOR=#666666]-=[/COLOR] horrSpeed;
                serverCommand([COLOR=#BA2121]'left'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'left'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]3[/COLOR];
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'right'[/COLOR])) {
                [COLOR=#408080][I]// Move to right[/I][/COLOR]
                [COLOR=#008000][B]if[/B][/COLOR] (self.player.scale.x [COLOR=#666666]>[/COLOR] [COLOR=#666666]0[/COLOR]) {
                    self.player.scale.x [COLOR=#666666]*=[/COLOR] [COLOR=#666666]-1[/COLOR];
                }
                newPos.x [COLOR=#666666]+=[/COLOR] horrSpeed;
                serverCommand([COLOR=#BA2121]'right'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'right'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]3[/COLOR];
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'up'[/COLOR])) {
                [COLOR=#408080][I]// Move up[/I][/COLOR]
                newPos.y [COLOR=#666666]-=[/COLOR] vertSpeed;
                serverCommand([COLOR=#BA2121]'up'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'up'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]6[/COLOR];
            } [COLOR=#008000][B]else[/B][/COLOR] [COLOR=#008000][B]if[/B][/COLOR] (self.key.isDown([COLOR=#BA2121]'down'[/COLOR])) {
                [COLOR=#408080][I]// Move down[/I][/COLOR]
                newPos.y [COLOR=#666666]+=[/COLOR] vertSpeed;
                serverCommand([COLOR=#BA2121]'down'[/COLOR]);
                self.player.animations.play([COLOR=#BA2121]'down'[/COLOR]);
                self.stillFrame [COLOR=#666666]=[/COLOR] [COLOR=#666666]0[/COLOR];
            } [COLOR=#008000][B]else[/B][/COLOR] {
                [COLOR=#408080][I]// Player not moving[/I][/COLOR]
                self.player.animations.stop();
                self.player.frame [COLOR=#666666]=[/COLOR] self.stillFrame;
            }
            main.game.add.tween(self.player).
                to(newPos, moveTime [COLOR=#666666]*[/COLOR] tweenTime, [COLOR=#BA2121]'Linear'[/COLOR]).
                start();
            main.game.add.tween(self.text).
                to({
                    x[COLOR=#666666]:[/COLOR] newPos.x [COLOR=#666666]-[/COLOR] [COLOR=#666666]2[/COLOR], 
                    y[COLOR=#666666]:[/COLOR] newPos.y [COLOR=#666666]-[/COLOR] [COLOR=#666666]60[/COLOR]
                }, moveTime [COLOR=#666666]*[/COLOR] tweenTime, [COLOR=#BA2121]'Linear'[/COLOR]).
                start();
        }
    };
    self.render [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {};
    comms.on([COLOR=#BA2121]'player'[/COLOR], [COLOR=#008000][B]function[/B][/COLOR] (data) {
        self.playerData [COLOR=#666666]=[/COLOR] data;
    });
    comms.on([COLOR=#BA2121]'player-move'[/COLOR], [COLOR=#008000][B]function[/B][/COLOR] (data) {
        console.log([COLOR=#BA2121]'player-move:'[/COLOR], data.game);
        [COLOR=#008000][B]var[/B][/COLOR] lag [COLOR=#666666]=[/COLOR] [COLOR=#008000]Date[/COLOR].now() [COLOR=#666666]-[/COLOR] data.time;
        self.playerData.game [COLOR=#666666]=[/COLOR] data.game;
        console.log([COLOR=#BA2121]"lag:"[/COLOR], lag);
        serverStep [COLOR=#666666]=[/COLOR] data.step;
    });
}


And we have a player walking around :)

Next, we'll get other players walking around! Oh, by the way, if you want to simulate lag, put the code in serverCommand() inside of a setTimeout(). Then, every time the client sends the server data it will be whatever ms behind you decide to set. Setting a random number between 0 and 1000 is lot's of fun.

Code:
    [COLOR=#008000][B]function[/B][/COLOR] serverCommand(command) {
        [COLOR=#008000][B]var[/B][/COLOR] t [COLOR=#666666]=[/COLOR] [COLOR=#008000]Math[/COLOR].floor([COLOR=#008000]Math[/COLOR].random() [COLOR=#666666]*[/COLOR] [COLOR=#666666]1000[/COLOR]);
        [COLOR=#008000][B]var[/B][/COLOR] data;
        step [COLOR=#666666]+=[/COLOR] [COLOR=#666666]1[/COLOR];
        data [COLOR=#666666]=[/COLOR] {time[COLOR=#666666]:[/COLOR] [COLOR=#008000]Date[/COLOR].now(), action[COLOR=#666666]:[/COLOR] command, step[COLOR=#666666]:[/COLOR] step};
        setTimeout([COLOR=#008000][B]function[/B][/COLOR] () {
            comms.emit([COLOR=#BA2121]'player-input'[/COLOR], data);
        }, t);
    }


Setting t to something sane, like 500, or even 2500- will be like artificial lag. Setting t to random like above, will cause the packets to be sent out of order, and since we're assuming sanity, our server/client isn't setup for that environment- and our programs simply can't handle it the way they are coded. In practice, the packets will never be continuously sent out of order and at different time intervals like the above test-case, so we won't plan on that happening on our server/client. But, it is fun to see what happens ;)

For a realistic test, try setting t to 500, and view the console.log results- the animations are fairly responsive and smooth. We'll test like this later when we test multiplayer in the next tutorial.

Previous (Players)
Table of Contents (Introduction)
Next (Players 3)
 
Last edited:
Back
Top