
Using Phaser.io
This is part 4 in my article series about how I created āPizza Peersā.
Havenāt read the other entries? Go to the devlog overview.
So far, weāve created a system that allows connecting to a server, creating a room, and then directly connecting all players within that room (via peer-to-peer).
Now we just need a game that can send and receive information over those connections.
For browser games, thereās really no better option than the free Phaser library. Iāve been using it since the day it was created, and itās only gained popularity (and features) since then.
(This surprised me a bit, to be honest. Feels like ages ago that I first learnt how to make browser games. I almost feel like a proud daddy who watched his kid grow and become one of the most used gaming libraries.)
I must say, however, that I havenāt used Phaser the past couple of years. (For the simple reason that I wasnāt creating browser games.) Since then, version 3 was released, which made a ton of changes to the overall design and structure of the framework.
After using it for this game, I must say that Phaser is still awesome and that version 3 is again a leap forward! However, because it was my first time working with v3, my code probably canāt be called āoptimizedā or ābest practiceā.
Anyway, letās get started!
Remark: By default, Phaser is a 2D library, but there are extensions for 3D in which I have a great interest. Hopefully Iāll be able to try these out soon and report the results.
Initializing the game
I usually make the game full screen by putting it inside an absolutely positioned <div> element.
The rest of the webpage functions as an āoverlayā on top of that.
This is one great advantage of browser games: they can use canvas and regular website stuff combined. For example, creating buttons, or links, or responsive UIs is really easy in website code. So Iād rather place those things in an overlay with minimal effort, than try to recreate it inside Phaser.
So, all the buttons for ācreate gameā and ājoin gameā and all that jazz are default HTML code. They are within the overlay and I will not discuss them here.
Once ācreate gameā is pressed, however, a function āstartPhaser()ā is called that does the following:
1function startPhaser(connection) {
2 // initialize Phaser game
3 // inside a div with id "phaser-game"
4 var config = {
5 type: Phaser.AUTO,
6 width: '100%',
7 height: '100%',
8 backgroundColor: '#8EB526',
9 parent: 'phaser-game',
10 scene: [MainGame, GameOver],
11 physics: {
12 default: 'arcade',
13 arcade: {
14 debug: false,
15 }
16 },
17 pixelArt: true,
18 antialias: false,
19 }
20
21 // create the game and save it in a GLOBAL variable, accessible anywhere
22 window.GAME = new Phaser.Game(config);
23
24 // Start both scenes (the "gameOver" scene also holds the welcome screen and lobby screen)
25 GAME.scene.start('mainGame', { roomCode: connection.room });
26 GAME.scene.start('gameOver', {});
27}
Youāll see here that GAME is a global variable. This is just an easy way to make it accessible in all modules ā you can do this differently if you want to. Global variables are generally bad practice.
Phaser works with scenes, which you can also view as āmodulesā or ācomponentsā. You can have as many scenes active as you like, you can toggle them on/off when you want, etcetera. If you want, you can structure your code to the extreme, creating a single scene for every bit of functionality. But weāll keep it simple here.
The scenes mainGame and gameOver do what you think they do. We will not look at the game over scene, as itās not interesting (and the source code is extremely self-explanatory).
Instead, over the course of the next few articles, weāll look at the critical parts of the āmain gameā.
Communication with p2p
How do we communicate over the internet?
Well, we need a two-way street:
When a certain player does something, we want to send a message over the corresponding peer.
When we receive a message from a peer, we want to relay that to the corresponding player.
During the game, we must keep a list that allows us to easily convert player <=> peer.
The game keeps a list of all players. When we create a new player, we save its index in the list on the peer.
- Yes, this means that the player list may never change order, but thatās a workable constraint.
Conversely, once the player is created, we save the peer on the player.
- This is easy: āplayer.myPeer = peerā
The addPlayer function becomes something like this:
1addPlayer: function(peer) {
2 // determine a random position for the player ...
3
4 // create new player (use graphics as base, turn into sprite within player group)
5 var newPlayer = this.playerBodies.create(randX, randY, 'dude');
6
7 // save player in array
8 this.players.push(newPlayer);
9
10 // save player index on peer
11 peer.playerGameIndex = (this.players.length - 1);
12
13 // and save peer on the player
14 newPlayer.myPeer = peer;
15
16 // ... and create a bunch of properties, settings, visual effects, and stuff for the player here
17},
NOTE: The variable this.players = [] is initialized when the scene is created. Iāll remind you again when we talk about randomly generating the city and game world.
Now, whenever we receive a signal, we can convert the peer to the corresponding player.
And whenever we want to send a signal, we can convert the player to its peer.
Thatās, in essence, all that is needed to communicate between the game and the p2p signals.
Moving players
To hammer home the concept, let me give you an example of the most basic input in the game: movement. (Which is also the first thing I implemented and tested.)
The smartphone sends a message to the computer (using their p2p connection). This message contains a movement vector.
The computer receives this message and calls āupdatePlayer()ā, which is a very simple function:
1// vec = a 2D movement vector;
2// vec[0] is movement over the X-axis, vec[1] over the Y-axis
3
4updatePlayer: function(peer, vec) {
5 // ... do some error checking to make sure the player exists and vector is valid ...
6
7 // grab the player (because we know the peer!)
8 var player = this.players[peer.playerGameIndex];
9
10 // just move the player according to velocity vector
11 player.setVelocity(vec[0] * speed, vec[1] * speed);
12
13 // ... animate some stuff here, dust particles when moving, etc ...
14},
Thatās it!
Well, weāre not completely done yet. I still havenāt shown you how to create the interface on the phone and send these messages. Guess what: weāre going to do that in part 5!
See you there.