Create a Basic Multiplayer Game in Phaser 3 with Socket.io – Part 1

In this multipart tutorial, we will be using Phaser 3 and Socket.io to create a simple multiplayer game. For our multiplayer game, we will follow the client-server game architecture. If you are not familiar with the client-server game architecture, the client is responsible for displaying the game to the player, handling the player’s input, and for communicating with the server. The server, on the other hand, is responsible for broadcasting that data to each client.

The goal of this tutorial is to teach you the basics of creating a multiplayer game. You will learn how to:

  • Setup a Node.js and Express server that will render our game and communicate with it.
  • Setup a basic Phaser 3 game that will act as our client.
  • Use Socket.io to allow the server and the client to communicate.

Need to explore some Phaser basics first? Try out Zenva’s Phaser Mini-Degree to learn how to make platforms, virtual pet games, RPGs, and more!

Course Files and Versions

You can download all of the files associated with the source code for part one 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
CTA Small Image
FREE COURSES AT ZENVA
LEARN GAME DEVELOPMENT, PYTHON AND MORE
ACCESS FOR FREE
AVAILABLE FOR A LIMITED TIME ONLY

Tutorial Requirements

For this tutorial, we will be using Node.js and Express to create our server. We will also be using NPM to install the required packages we need for the server to run. 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. 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.

Of course, you should also have some experience with Phaser. If this is your first time with Phaser, you can discover more Phaser basics with Zenva’s Phaser Mini-Degree learning pathway.

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 inside this folder and create a new file called server.js. Open up server.js and add the following code to it:

var express = require('express');
var app = express();
var 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:

  • 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 express.static built-in 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.

Before we can run the server, we will need to install the required modules for the server. Open your terminal/command prompt, and navigate to your project folder. Once there you will need to 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.

Now, we will install express. In your terminal run the following command: npm install –save express. This will create a folder called node_modules in your project folder, and by adding the –save flag to the command, npm will save this package in our package.json file.

Setting up the client

With the basic server code finished, we will now work on setting up our client-side code. In your project 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="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
        <script src="js/game.js"></script>
    </body>

</html>

In the code above, we set up a simple HTML page and we referenced two JavaScript files, phaser.min.js (the phaser game framework) and game.js (our Phaser game code). Back in the public folder, create a new folder called js , and in this folder create a new file called game.js. Open up game.js and add the following code to it:

var config = {
  type: Phaser.AUTO,
  parent: 'phaser-example',
  width: 800,
  height: 600,
  physics: {
    default: 'arcade',
    arcade: {
      debug: false,
      gravity: { y: 0 }
    }
  },
  scene: {
    preload: preload,
    create: create,
    update: update
  } 
};

var game = new Phaser.Game(config);

function preload() {}

function create() {}

function update() {}

Let’s review the code we just added:

  • We created the configuration that will be used for our Phaser game.
  • In the config object, in the type field, we set the renderer type for our game. The two main types are Canvas and WebGL. WebGL is a faster renderer and has better performance, but not all browsers support it. By choosing AUTO for the type, Phaser will use WebGL if it is available, otherwise, it will use Canvas.
  • In the config object, the parent field is used to tell Phaser to render our game in an existing <canvas>  element with that id if it exists. If it does not exists, then Phaser will create a <canvas>  element for us.
  • In the config object, we specify the width and height of the viewable area of our game.
  • In the config object, we enabled the arcade physics that is available in Phaser, and we set the gravity to 0.
  • In the config object, we embedded a scene object which will use the preload, update, and create functions we defined.
  • Lastly, we passed our config object to Phaser when we created the new game instance.

With our basic client-side code setup, 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.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 a black box on the web page, and if you open the console in the developer tools, you should see a log with the version of Phaser your game is running.

Blank Phaser 3 game running in Google Chrome

Adding Socket.IO

With our server now rendering our game, we will now work on adding Socket.IO to our game. If you are not familiar with Socket.IO, it is a JavaScript library that enables real-time, bi-directional communication between web clients and servers. To use Socket.IO, we need to update our client and server code to enable the communication between the two.

Back in your terminal, run the following command: npm install –save socket.io. If your server is still running, you can either: open a new terminal window and run the code in your project folder, or stop the server (CTRL + C) and then run the command. This will install the Socket.IO node package and save it in our package.json file.

Now, in server.js add the following code below the var server = require(‘http’).Server(app); line:

var io = require('socket.io').listen(server);

Then add the following code above the server.listen line:

io.on('connection', function (socket) {
  console.log('a user connected');
  socket.on('disconnect', function () {
    console.log('user disconnected');
  });
});

In the code above we:

  • referenced the socket.io module and had it listen to our server object.
  • added logic to listen for connections and disconnections.

Next, we will update the client side code to include the Socket.IO library. Open up index.html and add the following line at the top of the <body> element:

<script src="/socket.io/socket.io.js"></script>

Then, open up game.js and add the following line inside the create function:

this.socket = io();

Now, if you start the server back up again, and refresh your game in your browser, you should see the user connected/disconnected messages in your terminal.

Console information denoting users connecting and disconnecting

Adding players – Server

Now that we have our socket connections setup, we can move on to adding players to our game. In order to keep all of the player’s games in sync, we will need to notify all players when a user connects or disconnects from the game. Also, when a new player connects we will need a way to let the player know of all the other players in the game. To do all of this we will need to store some player data, and we will use the socket connections to send messages to our players.

For this tutorial, we will store the player data in memory on the server. Normally, we would want to store this data in some type of database, that way it would be persistent, and if the server fails, we could easily recover the state of the game.

In server.js add the following line below the io variable:

var players = {};

We will use this object to keep track of all the players that are currently in the game. Next, in the callback function of the socket.io connection event add the following code below the console.log(‘a user connected’); line:

// create a new player and add it to our players object
players[socket.id] = {
  rotation: 0,
  x: Math.floor(Math.random() * 700) + 50,
  y: Math.floor(Math.random() * 500) + 50,
  playerId: socket.id,
  team: (Math.floor(Math.random() * 2) == 0) ? 'red' : 'blue'
};
// send the players object to the new player
socket.emit('currentPlayers', players);
// update all other players of the new player
socket.broadcast.emit('newPlayer', players[socket.id]);

Let’s review the code we just added:

  • When a player connects to the web socket, we store some player data in the players object and we use the socket.id as the key.
  • We are storing the rotation, x, and y position of the player, and we will use this to control were we create sprites on the client side, and use this data to update all players games. We also store the playerId so we can reference it in the game, and we added a team attribute that will be used later.
  • We used socket.emit and socket.broadcast.emit to emit an event to the client side socket. socket.emit will only emit the event to this particular socket (the new player that just connected).  socket.broadcast.emit will send the event to all other sockets (the existing players).
  • In the currentPlayers event, we are passing the players object to the new player. This data will be used to populate all of the player sprites in the new player’s game.
  • In the newPlayer event, we are the passing the new player’s data to all other players, that way the new sprite can be added to their game.

When a player disconnects, we need to remove that player’s data from our players object, and we need to emit a message to all other players about this user leaving, that way we can remove that player’s sprite from the game.

In the callback function of the socket.io disconnect event add the following code below the console.log(‘user disconnected’); line:

// remove this player from our players object
delete players[socket.id];
// emit a message to all players to remove this player
io.emit('disconnect', socket.id);

Your server.js file should look like the following:

var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io').listen(server);

var players = {};

app.use(express.static(__dirname + '/public'));

app.get('/', function (req, res) {
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function (socket) {
  console.log('a user connected');
  // create a new player and add it to our players object
  players[socket.id] = {
    rotation: 0,
    x: Math.floor(Math.random() * 700) + 50,
    y: Math.floor(Math.random() * 500) + 50,
    playerId: socket.id,
    team: (Math.floor(Math.random() * 2) == 0) ? 'red' : 'blue'
  };
  // send the players object to the new player
  socket.emit('currentPlayers', players);
  // update all other players of the new player
  socket.broadcast.emit('newPlayer', players[socket.id]);

  // when a player disconnects, remove them from our players object
  socket.on('disconnect', function () {
    console.log('user disconnected');
    // remove this player from our players object
    delete players[socket.id];
    // emit a message to all players to remove this player
    io.emit('disconnect', socket.id);
  });
});

server.listen(8081, function () {
  console.log(`Listening on ${server.address().port}`);
});

Conclusion

With our server code for adding players in place, this brings part one of this tutorial to an end. In part two we wrap up our multiplayer game by:

  • Adding the client side logic for adding players to our game.
  • Adding logic for player input.
  • Adding collectibles for the players to collect.

I hoped you enjoyed part one of this tutorial and found it helpful.

Note that if you feel a bit out of depth, don’t be afraid to explore some basic web development courses first for individuals or explore Phaser courses focused on basic Phaser mechanics and usage.

Did you come across any errors in this tutorial? Please let us know by completing this form and we’ll look into it!

FREE COURSES
Python Blog Image

FINAL DAYS: Unlock coding courses in Unity, Godot, Unreal, Python and more.