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!

4.) MMO From Scratch - The Client

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

The project we have up until this point is on github:


The client for Surface Tension uses jQuery, Socket.io and Phaser to render a game world in HTML5.

The client is responsible for storing cookies, accepting packets from the server, and sending commands to the server. We're using the authoritative server, and dumb client approach for this project. We're going to talk about latency correction, and we'll simulate lag to test that it works.

Let us start with the cookies library. I used some code from for this project, and modified it for my needs.
/public/js/cookies.js
Code:
[COLOR=#008000][B]var[/B][/COLOR] cookies [COLOR=#666666]=[/COLOR] {
    create[COLOR=#666666]:[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] (name, value, days) {
        [COLOR=#008000][B]var[/B][/COLOR] date, expires;
        [COLOR=#008000][B]if[/B][/COLOR] (days) {
            date [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]new[/B][/COLOR] [COLOR=#008000]Date[/COLOR]();
            date.setTime(date.getTime()[COLOR=#666666]+[/COLOR](days[COLOR=#666666]*24*60*60*1000[/COLOR]));
            expires [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]"; expires="[/COLOR][COLOR=#666666]+[/COLOR]date.toGMTString();
        } [COLOR=#008000][B]else[/B][/COLOR] {
            expires [COLOR=#666666]=[/COLOR] [COLOR=#BA2121]""[/COLOR];
        }
        [COLOR=#008000]document[/COLOR].cookie [COLOR=#666666]=[/COLOR] name[COLOR=#666666]+[/COLOR][COLOR=#BA2121]"="[/COLOR][COLOR=#666666]+[/COLOR]value[COLOR=#666666]+[/COLOR]expires[COLOR=#666666]+[/COLOR][COLOR=#BA2121]"; path=/"[/COLOR];
    },
    read[COLOR=#666666]:[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] (name) {
        [COLOR=#008000][B]var[/B][/COLOR] nameEQ [COLOR=#666666]=[/COLOR] name [COLOR=#666666]+[/COLOR] [COLOR=#BA2121]"="[/COLOR];
        [COLOR=#008000][B]var[/B][/COLOR] ca [COLOR=#666666]=[/COLOR] [COLOR=#008000]document[/COLOR].cookie.split([COLOR=#BA2121]';'[/COLOR]);
        [COLOR=#008000][B]var[/B][/COLOR] i, c;
        [COLOR=#008000][B]for[/B][/COLOR](i[COLOR=#666666]=0[/COLOR];i [COLOR=#666666]<[/COLOR] ca.length;i[COLOR=#666666]++[/COLOR]) {
            c [COLOR=#666666]=[/COLOR] ca[i];
            [COLOR=#008000][B]while[/B][/COLOR] (c.charAt([COLOR=#666666]0[/COLOR])[COLOR=#666666]==[/COLOR][COLOR=#BA2121]' '[/COLOR]) {
                c [COLOR=#666666]=[/COLOR] c.substring([COLOR=#666666]1[/COLOR],c.length);
            }
            [COLOR=#008000][B]if[/B][/COLOR] (c.indexOf(nameEQ) [COLOR=#666666]==[/COLOR] [COLOR=#666666]0[/COLOR]) {
                [COLOR=#008000][B]return[/B][/COLOR] c.substring(nameEQ.length,c.length);
            }
        }
        [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]null[/B][/COLOR];
    },
    erase[COLOR=#666666]:[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] (name) {
        cookies.create(name, [COLOR=#BA2121]""[/COLOR], [COLOR=#666666]-1[/COLOR]);
    }
}
I'll be using Socket.io to send/receive forms, and to send HTML content. I create a variable called 'comms' which is short for communications, that is the socket. When a form with [method="socket"] is submitted, the action of that form is used to determine the name of the socket channel to send to the server, and the form data is serialized and sent as well. Checkboxes are converted to true/false values. I'll update this function later to suit our needs. The server can send content to the client, which has a selector (the CSS selector for jQuery to use), and the HTML to put inside of the element(s) that is/are selected.
/public/js/io.js
Code:
[COLOR=#008000][B]var[/B][/COLOR] comms [COLOR=#666666]=[/COLOR] ([COLOR=#008000][B]function[/B][/COLOR] () {
    [COLOR=#008000][B]var[/B][/COLOR] socket [COLOR=#666666]=[/COLOR] io();

    $([COLOR=#BA2121]'html'[/COLOR]).on([COLOR=#BA2121]'submit'[/COLOR], [COLOR=#BA2121]'form[method="socket"]'[/COLOR], [COLOR=#008000][B]function[/B][/COLOR] (e) {
        [COLOR=#008000][B]var[/B][/COLOR] formData [COLOR=#666666]=[/COLOR] [];
        $([COLOR=#008000][B]this[/B][/COLOR]).find([COLOR=#BA2121]'input[name]'[/COLOR]).each([COLOR=#008000][B]function[/B][/COLOR] () {
            [COLOR=#008000][B]var[/B][/COLOR] type [COLOR=#666666]=[/COLOR] $([COLOR=#008000][B]this[/B][/COLOR]).attr([COLOR=#BA2121]'type'[/COLOR]);
            [COLOR=#008000][B]var[/B][/COLOR] result [COLOR=#666666]=[/COLOR] {name[COLOR=#666666]:[/COLOR] $([COLOR=#008000][B]this[/B][/COLOR]).attr([COLOR=#BA2121]'name'[/COLOR]), value[COLOR=#666666]:[/COLOR] [COLOR=#008000][B]void[/B][/COLOR] [COLOR=#666666]0[/COLOR]};
            [COLOR=#008000][B]switch[/B][/COLOR] (type) {
                [COLOR=#008000][B]case[/B][/COLOR] [COLOR=#BA2121]'checkbox'[/COLOR][COLOR=#666666]:[/COLOR]
                    result.value [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]this[/B][/COLOR].checked.toString();
                    [COLOR=#008000][B]break[/B][/COLOR];
                [COLOR=#008000][B]default[/B][/COLOR][COLOR=#666666]:[/COLOR]
                    result.value [COLOR=#666666]=[/COLOR] $([COLOR=#008000][B]this[/B][/COLOR]).val();
            }
            formData.push(result);
        });
        console.log([COLOR=#BA2121]'action:'[/COLOR], $([COLOR=#008000][B]this[/B][/COLOR]).attr([COLOR=#BA2121]'action'[/COLOR]), formData);
        socket.emit($([COLOR=#008000][B]this[/B][/COLOR]).attr([COLOR=#BA2121]'action'[/COLOR]), formData);
        e.stopPropagation();
        e.preventDefault();
        [COLOR=#008000][B]return[/B][/COLOR] [COLOR=#008000][B]false[/B][/COLOR];
    });

    socket.on([COLOR=#BA2121]'content'[/COLOR], [COLOR=#008000][B]function[/B][/COLOR] (data) {
        console.log([COLOR=#BA2121]"content:"[/COLOR], data);
        $(data.selector).html(data.html);
    });

    socket.on([COLOR=#BA2121]'cookie'[/COLOR], [COLOR=#008000][B]function[/B][/COLOR] (data) {
        console.log([COLOR=#BA2121]"setting cookie:"[/COLOR], data);
        cookies.create(data.name, data.value, data.days);
    });

    [COLOR=#008000][B]return[/B][/COLOR] socket;
}());
The game client code will be in /public/js/main.js

Here is the index.html:
Code:
[COLOR=#BC7A00]<!doctype html>[/COLOR]
[COLOR=#008000][B]<html>[/B][/COLOR]
[COLOR=#008000][B]<head>[/B][/COLOR]
    [COLOR=#008000][B]<meta[/B][/COLOR] [COLOR=#7D9029]charset=[/COLOR][COLOR=#BA2121]"UTF-8"[/COLOR] [COLOR=#008000][B]/>[/B][/COLOR]
    [COLOR=#008000][B]<title>[/B][/COLOR]Surface Tension[COLOR=#008000][B]</title>[/B][/COLOR]
    [COLOR=#008000][B]<link[/B][/COLOR] [COLOR=#7D9029]rel=[/COLOR][COLOR=#BA2121]"stylesheet"[/COLOR] [COLOR=#7D9029]href=[/COLOR][COLOR=#BA2121]"/css/main.css"[/COLOR][COLOR=#008000][B]></link>[/B][/COLOR]
[COLOR=#008000][B]</head>[/B][/COLOR]
[COLOR=#008000][B]<body>[/B][/COLOR]
[COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"gameShell"[/COLOR][COLOR=#008000][B]>[/B][/COLOR]
    [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"canvas"[/COLOR][COLOR=#008000][B]>[/B][/COLOR]
        
    [COLOR=#008000][B]</div>[/B][/COLOR]
    [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"rightPanel"[/COLOR][COLOR=#008000][B]></div>[/B][/COLOR]
    [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]style=[/COLOR][COLOR=#BA2121]"clear:both;"[/COLOR][COLOR=#008000][B]></div>[/B][/COLOR]
    [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"bottomPanel"[/COLOR][COLOR=#008000][B]>[/B][/COLOR]
        [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"chatPanel"[/COLOR][COLOR=#008000][B]>[/B][/COLOR]
            [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"chatBox"[/COLOR][COLOR=#008000][B]>[/B][/COLOR]

            [COLOR=#008000][B]</div>[/B][/COLOR]
            [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"chatBtn"[/COLOR][COLOR=#008000][B]>[/B][/COLOR]
            [COLOR=#008000][B]</div>[/B][/COLOR]
        [COLOR=#008000][B]</div>[/B][/COLOR]
        [COLOR=#008000][B]<div[/B][/COLOR] [COLOR=#7D9029]id=[/COLOR][COLOR=#BA2121]"infoBox"[/COLOR][COLOR=#008000][B]>[/B][/COLOR]
        [COLOR=#008000][B]</div>[/B][/COLOR]
    [COLOR=#008000][B]</div>[/B][/COLOR]
[COLOR=#008000][B]</div>[/B][/COLOR]
    [COLOR=#008000][B]<script [/B][/COLOR][COLOR=#7D9029]src=[/COLOR][COLOR=#BA2121]"/js/cookies.js"[/COLOR][COLOR=#008000][B]></script>[/B][/COLOR]
    [COLOR=#008000][B]<script [/B][/COLOR][COLOR=#7D9029]src=[/COLOR][COLOR=#BA2121]"/socket.io/socket.io.js"[/COLOR][COLOR=#008000][B]></script>[/B][/COLOR]
    [COLOR=#008000][B]<script [/B][/COLOR][COLOR=#7D9029]src=[/COLOR][COLOR=#BA2121]"/js/jquery.js"[/COLOR][COLOR=#008000][B]></script>[/B][/COLOR]
    [COLOR=#008000][B]<script [/B][/COLOR][COLOR=#7D9029]src=[/COLOR][COLOR=#BA2121]"/js/io.js"[/COLOR][COLOR=#008000][B]></script>[/B][/COLOR]
    [COLOR=#008000][B]<script [/B][/COLOR][COLOR=#7D9029]src=[/COLOR][COLOR=#BA2121]"/js/phaser.min.js"[/COLOR][COLOR=#008000][B]></script>[/B][/COLOR]
    [COLOR=#008000][B]<script [/B][/COLOR][COLOR=#7D9029]src=[/COLOR][COLOR=#BA2121]"/js/main.js"[/COLOR][COLOR=#008000][B]></script>[/B][/COLOR]
[COLOR=#008000][B]</body>[/B][/COLOR]
[COLOR=#008000][B]</html>[/B][/COLOR]

So I went over the custom libraries, all of the files are on , so now we'll work on /public/js/main.js.

If you were to run the server and view this in a browser now, it wouldn't be that interesting. Let's change that in this tutorial.

Now, in /public/js/main.js - we're going to start phaser.
Code:
[COLOR=#008000][B]var[/B][/COLOR] game [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]new[/B][/COLOR] Phaser.Game([COLOR=#666666]1000[/COLOR], [COLOR=#666666]450[/COLOR], Phaser.AUTO, [COLOR=#BA2121]'canvas'[/COLOR], {
    preload[COLOR=#666666]:[/COLOR] preload, 
    create[COLOR=#666666]:[/COLOR] create,
    update[COLOR=#666666]:[/COLOR] update,
    render[COLOR=#666666]:[/COLOR] render
});

[COLOR=#008000][B]function[/B][/COLOR] preload() {}
[COLOR=#008000][B]function[/B][/COLOR] create() {}
[COLOR=#008000][B]function[/B][/COLOR] update() {}
[COLOR=#008000][B]function[/B][/COLOR] render() {}
I'll summarize how Phaser works very quickly.

  • Preload is done before the canvas is created- that's where you start loading all your assets and stuff.
  • Create is done after the canvas is created- and is where you start creating your game objects.
  • Update is run on every tick of the game-loop. All of the code in update should execute in under 16ms.
  • Render is run on every tick, right after update. We should only use render for testing purposes.

We're going to use a plugin style to get our game objects to Phaser. Let's start with the grass background.
First, let's make a folder to put our game objects- /public/js/objects
Second, let's create a file called /public/js/objects/map.js
We'll use this skeleton for all of our objects:
Code:
[COLOR=#008000][B]function[/B][/COLOR] Map (main) {
    [COLOR=#BA2121]"use strict"[/COLOR];
    [COLOR=#008000][B]var[/B][/COLOR] self [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]this[/B][/COLOR];
    self.preload [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {};
    self.create [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {};
    self.update [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {};
    self.render [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {};
}
Everytime we create a new public JS file, we should include it in index.html before we include main.js
Code:
[COLOR=#008000][B]<script [/B][/COLOR][COLOR=#7D9029]src=[/COLOR][COLOR=#BA2121]"/js/objects/map.js"[/COLOR][COLOR=#008000][B]></script>[/B][/COLOR]

Now, we don't want to load the game before the user logs in, so we should put everything in main.js inside of a function to be executed later. We're going to have several game plugins each with a preload, create, update, and render. So we're going to put all of these plugins into an array, and loop through each of them and execute the corresponding functions for Phaser.

/public/js/main.js
Code:
[COLOR=#008000][B]function[/B][/COLOR] initializeGame (main) {
    main.game [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]new[/B][/COLOR] Phaser.Game([COLOR=#666666]1000[/COLOR], [COLOR=#666666]450[/COLOR], Phaser.AUTO, [COLOR=#BA2121]'canvas'[/COLOR], {
        preload[COLOR=#666666]:[/COLOR] preload, 
        create[COLOR=#666666]:[/COLOR] create,
        update[COLOR=#666666]:[/COLOR] update,
        render[COLOR=#666666]:[/COLOR] render
    });

    [COLOR=#008000][B]var[/B][/COLOR] plugins [COLOR=#666666]=[/COLOR] [[COLOR=#008000][B]new[/B][/COLOR] Map(main)];

    [COLOR=#008000][B]function[/B][/COLOR] preload() {
        plugins.forEach([COLOR=#008000][B]function[/B][/COLOR] (plugin) {
            plugin.preload();
        });
    }
    [COLOR=#008000][B]function[/B][/COLOR] create() {
        plugins.forEach([COLOR=#008000][B]function[/B][/COLOR] (plugin) {
            plugin.create();
        });
    }
    [COLOR=#008000][B]function[/B][/COLOR] update() {
        plugins.forEach([COLOR=#008000][B]function[/B][/COLOR] (plugin) {
            plugin.update();
        });
    }
    [COLOR=#008000][B]function[/B][/COLOR] render() {
        plugins.forEach([COLOR=#008000][B]function[/B][/COLOR] (plugin) {
            plugin.render();
        });
    }
}
Now, all of the game plugins may share and manipulate game state.

Let's load some grass. Our grass graphic is in /public/assets/game/grass.png. To load an image in phaser, we use game.load.image(<name:string>, <srcImage:urlString>). If you notice, we pass main to every plugin, and we assign the game variable to main. So in the plugin, we can simply use main.game to get the game object.
/public/js/objects/map.js:[4-6]
Code:
    self.preload [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        main.game.load.image([COLOR=#BA2121]'grass'[/COLOR], [COLOR=#BA2121]'./assets/game/grass.png'[/COLOR]);
    };
We should create a group for map tiles since there are going to be more than one of these, and they all have the same physics specifications. After that, we'll try to add a grass tile to the map. We do that in create.
/public/js/objects/map.js[7-10]
Code:
    self.create [COLOR=#666666]=[/COLOR] [COLOR=#008000][B]function[/B][/COLOR] () {
        self.map [COLOR=#666666]=[/COLOR] main.game.add.group();
        self.map.create([COLOR=#666666]0[/COLOR], [COLOR=#666666]0[/COLOR], [COLOR=#BA2121]'grass'[/COLOR]);
    };
Now (if we call the initializeGame(..) in main.js) we have a single grass tile (512x512). That's great, but let's get a login working, and a database set up so that we can start loading assets from the server.

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