In this multi-part tutorial, we will be creating a simple multiplayer game with Phaser and Socket.io. For our multiplayer game, we will follow the client-server game architecture and we will be setting up Phaser to run on our server and we will be using that as an authoritative server. If you are not familiar with the client-server game architecture, it goes like this: the client is responsible for displaying the game to the player, handling the player’s input, and for sending that data to the server. Our authoritative server will be responsible for running the main Phaser logic, and it will be responsible for sending the data to each client.
The goal of this tutorial is to teach you the basics of creating a multiplayer game with an authoritative server. You will learn how to:
- Set up a Node.js and Express server that will act as our authoritative server. This server will also be responsible for rendering our client-side files.
- Set up and run Phaser in Headless mode on the server.
- Set up a basic Phaser 3 game that will act as our client.
- Use Socket.IO to allow the server and client to communicate with each other.
Course Files and Versions
You can download all of the files associated with the source code for Part 1 here.
At the time this tutorial was written, the following versions were used. You may need to use these versions to have the same results from this tutorial.
- Node.js: 10.13.0
- JSDOM: 13.0.0
- Express: 4.16.4
- Socket.IO: 2.1.1
- Datauri: 1.1.0
BUILD GAMES FINAL DAYS: Unlock 250+ coding courses, guided learning paths, help from expert mentors, and more.
Tutorial Requirements
For this tutorial, we will be using Node.js and npm to install the required packages that are needed for this project. In order to follow along with this tutorial, you will need to have Node.js and NPM installed locally, or you will need access to an environment that already has them installed. We will also be using the Command Prompt (Windows) / Terminal (Mac) to install the required packages, and to start/stop our Node server.
Having a prior experience with these tools is a plus, but it is not required for this tutorial. We will not be covering how to install these tools as the focus of this tutorial is making a game with Phaser. You will also need access to the Chrome Web Browser to follow along with this tutorial. The last thing you will need is an IDE or Text Editor for editing your code.
To install Node.js, click the link here: and choose the LTS version. You can download and use the current version with this tutorial, however, the LTS version is recommended for most users. When you install Node.js, NPM will also be installed on your computer. Once you have these tools installed, you can move on to the next part.
Also, at the time this tutorial was created, the latest LTS version of Node.js was v10.13.0
. You can check your version of Node by running node -v
from the terminal. It is recommended you be on this or a new version when following along.
Setting up the Server
The first thing we are going to do is create a basic Node.js server that will serve our game files. To get started, create a new folder on your computer, it can be called anything you want. Then, navigate to this folder in the terminal and run the following command npm init -f
. This will create a package.json file in your project folder. We will use this file to keep track of all the packages that our project depends on.
The first package we will add to our project is express
, a Node.js web application framework that we will use for rendering our static client-side files. In the terminal, run the following command: npm install --save express
, which will install that package and its required dependencies in a node_modules
folder in our project. This command will also update our package.json
file by adding the express
module to it.
Now, in your project folder create a new folder called server
and in this folder create a new file called index.js
. Open index.js
and add the following code to it:
const express = require('express'); const app = express(); const server = require('http').Server(app); app.use(express.static(__dirname + '/public')); app.get('/', function (req, res) { res.sendFile(__dirname + '/index.html'); }); server.listen(8081, function () { console.log(`Listening on ${server.address().port}`); });
In the code above, we did the following:
- Referenced the express module, which is a web framework that will help us render our static files.
- Created a new instance of express and called it app.
- Supplied the app to the HTTP server, which will allow express to handle the HTTP requests.
- Updated the server to render our static files using the built-in express.static middleware function in Express.
- Told the server to serve the index.html file as the root page.
- Had the server start listening on port 8081.
Setting up the client
With the basic server code finished, we will now work on setting up our client-side code. In your server
folder, create a new folder called public. Any file we put in this folder will be rendered by the server that we set up. So we will want to put all of our static client-side files in this folder. Now inside the public folder, create a new file called index.html. Open up index.html and add the following code to it:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script> <script src="js/game.js"></script> </body> </html>
Then, in the public
folder create a new folder called js
and in this folder create a new file called game.js
. In this file, add the following code:
var config = { type: Phaser.AUTO, parent: 'phaser-example', width: 800, height: 600, scene: { preload: preload, create: create, update: update } }; var game = new Phaser.Game(config); function preload() {} function create() {} function update() {}
In the code above, we created a basic HTML file and referenced the Phaser library. Lastly, we created a new Phaser Game instance.
With our basic client-side code set up, we will now test our server and make sure everything is working correctly. Back in the terminal/command prompt, run the following command: node server/index.js and you should see the following line appear Listening on 8081. Now, if you open up your web browser and navigate to: http://localhost:8081/ , you should see black box on the web page, and if you open the console in the developer tools, you should see a log line with the version of Phaser your game is running.
Debugging
In the next step, we will be setting up our server to run Phaser in Headless mode, which require us to run a virtual dom on the server. However, before we do that one that, one thing we will go over first is debugging the Phaser code that is running on the server. In order to debug our code, we can use Node’s --inspect
flag and Chrome’s developer tools to view the console output of the dom we are running on the server. To do this you will need to stop and restart your server with the following command: node --inspect server/index.js
. Then, open up Chrome and visit the following URL: chrome://inspect/#devices
. You should see a screen similar to this:
Click on the Open dedicated DevTools for Node
link which will open a new browser window that will have a console that will display the output of the dom. By clicking the above link, even when you stop and restart your server that console will stay linked to your Node session and you should see the updated output.
Setting up the Authoritative Server
Now that we have Phaser running on the client side, we are going to work on getting Phaser running on the server. Since we will be running Phaser on the server, we will need to add a few additional libraries to our project to get it to work correctly. The first package we will need is jsdom, which is used to recreate most of the DOM JavaScript APIs from the browser and it will allow you to load HTML files and interact with them.
The second package we will need is node-canvas, which is an implementation of the canvas API in Node.js. The reason we need this package is that Phaser requires the canvas API to run, even when it is running in Headless mode. To install these packages, in your terminal run the following command: npm install --save canvas jsdom
.
Now that we have the required packages installed, we can start adding the code for running Phaser on the server. In your server
folder, create a new folder called authoritative_server
. In this folder create a new file called index.html
and add the following code to it:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> <body> <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script> <script src="js/game.js"></script> </body> </html>
Then, create a new folder called js
and in here create a new file called game.js
and add the following code to it:
const config = { type: Phaser.HEADLESS, parent: 'phaser-example', width: 800, height: 600, physics: { default: 'arcade', arcade: { debug: false, gravity: { y: 0 } } }, scene: { preload: preload, create: create, update: update } }; function preload() {} function create() {} function update() {} const game = new Phaser.Game(config);
This code should look similar to the client side code we added earlier. The main difference between the two is in our Phaser config we set the type to be Phaser.HEADLESS
.
With the Phaser server code in place, the next thing we need to do is tell the server to load and run these files. In the server
folder, open up index.js
and add the following code to the top of the file:
const path = require('path'); const jsdom = require('jsdom');
Then, add the following code below the server
variable:
const { JSDOM } = jsdom;
Finally, add the following code at the bottom of the file:
function setupAuthoritativePhaser() { JSDOM.fromFile(path.join(__dirname, 'authoritative_server/index.html'), { // To run the scripts in the html file runScripts: "dangerously", // Also load supported external resources resources: "usable", // So requestAnimatinFrame events fire pretendToBeVisual: true }); } setupAuthoritativePhaser();
In the code above, we did the following:
- First we included the
jsdom
package, which will allow us to use the DOM API on the server. - We then created a new function called
setupAuthoritativePhaser
and called that function. - In this function, we used JSDOM’s
fromFile
method to load theindex.html
we created earlier. When we called thefromFile
method, we pass it the file we would like to load and an object that contains the options we need when running this file. These options include:- allowing JSDOM to run scripts
- allowing JSDOM to load external resources
- telling JSDOM to behave like a normal visual browser
Now, if you save your code and restart your server you should see that Phaser is running on our server and it is running in Headless mode.
However, you should see an error message about the window.focus
method not being implemented. The reason this error is showing is that currently, this method is not implemented in jsdom
and anytime this method is called, this error will be displayed. In order to resolve this error, we just need to make a minor change config
object that is loaded by Phaser that is running on our server. In authoritative_server/js/game.js
, add the following property to the config
object:
autoFocus: false
This will tell Phaser to not call window.focus()
when the game first boots. By default, this value is set to true
. Before we move on to the next section, we need to make a minor change to our server code. Right now, our server starts listening and a client can connect to our server before Phaser on our server is up and running. Instead, we want to make sure that Phaser is running on our server before a client can connect.
To do this, we need to wait for our virtual DOM to be ready and then start our express server. Open up server/index.js
, and remove the following lines of code from the file:
server.listen(8081, function () { console.log(`Listening on ${server.address().port}`); });
Then, replace the `setupAuthoritativePhaser` function with the following code:
function setupAuthoritativePhaser() { JSDOM.fromFile(path.join(__dirname, 'authoritative_server/index.html'), { // To run the scripts in the html file runScripts: "dangerously", // Also load supported external resources resources: "usable", // So requestAnimatinFrame events fire pretendToBeVisual: true }).then((dom) => { dom.window.gameLoaded = () => { server.listen(8081, function () { console.log(`Listening on ${server.address().port}`); }); }; }).catch((error) => { console.log(error.message); }); }
Lastly, open authoritative_server/js/game.js
and add the following code at the bottom of the file:
window.gameLoaded();
Let’s review the code we just added:
- Since the
fromFile
method returns a promise, we can use.then()
to wait for the promise to resolve, and then have it invoke acallback
function. In this callback function, we added the logic for starting our server. - In
game.js
, we calledwindow.gameLoaded()
which we defined in the callback function above. Since we are loading the Phaser library and then creating our game object, we need to make sure that these things are done before we start our server as well, which is why we added this new function.
Now, if you save your changes and restart the server you should see that our Phaser game is created before we start our express server.
Conclusion
With Phaser running in headless mode on our server, this brings Part 1 to an end. In Part 2, we continue our multiplayer game by:
- Adding the Socket.IO library to our project.
- Adding the server logic for adding players to our game.
- Adding the server logic for removing players from our game.
- Adding the client side logic for adding the player to our game.
I hoped you enjoyed the start of this tutorial series and found it helpful. If you have any questions, or suggestions regarding what we should cover next, please let us know in the comments below.