Creating A Simple Multiplayer Game In Phaser 3 With An Authoritative Server – Part 2

Creating A Simple Multiplayer Game In Phaser 3 With An Authoritative Server – Part 2

In Part 1 of this tutorial, we created our Node.js server, set up a basic Phaser game, and set up our server to run Phaser in headless mode. If you missed the beginning, you can find it here. In this tutorial, we are going to focus on adding the Socket.IO library to game, adding the server logic for adding and removing players from the game, and adding the client side logic for adding a player to the game.

If you didn’t complete Part 1 and would like to continue from there, you can find the code for it here.

You can download all of the files associated with the source code for Part 2 here.

Let’s get started!

Adding Socket.IO

With Phaser now running on our server, 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:

Then, below the dom.window.gameLoaded code add the following line:

This will inject our socket.io instance into jsdom which will allow us to access it in our Phaser code that is running on the server. Now, in authoritative_server/js/game.js add the following code in the create function:

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 public/index.html and add the following line at the top of the  <body> element:

Then, open up public/js/game.js and add the following code in the create function:

Now, if you save your code changes, restart the server, and refresh your game in the browser you should see the message about a user being connected. If you refresh your game in the browser you should see a new message about a user disconnecting and then another message about a user connecting.

Adding players – Server

With our socket connection setup, we can move on to adding players to our game. The first thing we need to do is load the asset that will be used for the player’s ship. For this tutorial, we will be using some images from Kenny’s Space Shooter Redux asset pack. The asset for the game can be downloaded here.

In the authoritative_server folder, create a new folder called  assets and place the image there. To load the image in our game, you will need to add the following line inside the  preload function in authoritative_server/js/game.js:

With the ship image loaded, we will add the rest of the logic for adding the players. In order to keep all of the player’s games in sync, we will need a way 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 new player know of all the other players in the game.

To do this, we can use our socket connections to send messages to each client. For the player data, we will need to keep track of each player that connects and disconnects.

In, authoritative_server/js/game.js add the following code at the top of the file:

We will use this object to keep track of all the players that are currently in the game. Next, at the top of the create function add the following code:

Next, in the callback function of the socket.io  connection event add the following code below the  console.log('a user connected'); line:

Let’s review the code we just added:

  • We created a new variable called self and we use it to store a reference to this Phaser Scene.
  • We created a new Phaser physics group, which will be used to manage all of the players in our game. If you are not familiar with groups in Phaser, they are a way for us to manage similar game objects and control them as one unit. One example is, instead of having to check for collisions on each of those game objects separately, we can check for collision between the group and other game objects.
  • When a player connects to a web socket, we update the players object with some data about the player and we store this data as an object and we use the socket.id as the key for the object.
  • 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 each 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.
  • Lastly, we called a new function called addPlayer, which will be used for creating the player on the server.

Now, we will create the addPlayer function. This function will be used to create new player game objects and it will add those game objects to the player’s group we just created. Add the following code below the update function:

In the code above we:

  • Created a new player’s ship by using the x and y coordinates that we generated earlier.
  • Instead of just using  self.add.image to create our player’s ship, we used  self.physics.add.image in order to allow that game object to use the arcade physics.
  • We used  setOrigin() to set the origin of the game object to be in the middle of the object instead of the top left. The reason we did this because, when you rotate a game object, it will be rotated around the origin point.
  • We used  setDisplaySize() to change the size and scale of the game object. Originally, our ship image was 106×80 px, which was a little big for our game. After calling  setDisplaySize() the image is now 53×40 px in the game.
  • Lastly, we used  setDragsetAngularDrag, and  setMaxVelocity to modify how the game object reacts to the arcade physics. Both  setDrag and  setAngularDrag are used to control the amount of resistance the object will face when it is moving.  setMaxVelocity is used to control the max speed the game object can reach.

Removing players – Server

With the logic for adding players on the server in place, we will add the logic for removing a player from the server. 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 client’s game.

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

Then, add the following code below the addPlayer function:

In the code above we:

  • Created a new function called removePlayer. This function will take the socket.id of the player that disconnected and it will find that player’s game object in our Phaser group and it will destroy it.
  • We do this by calling the  getChildren() method on our  players group. The  getChildren() method will return an array of all the game objects that are in that group, and from there we use the  forEach() method to loop through that array until we find the game object with the matching id.
  • Once the game object is destroyed on the server, we then delete that player from our players object by using the delete operator to remove that property.
  • Finally, we used the io.emit to send a message to all sockets. In this event, we are passing the socket.id of the user that disconnected that way we can remove that player’s sprite from the client side code.

Your authoritative_server/js/game.js file should look like the following:

Now, if you save your code changes, restart the server, and refresh your game in the browser you should see an error message in your console.

From the looks of the error message, it looks like jsdom does not support the URL.createObjectURL method. This static method is used to create a DOMString containing an object URL that can be used to reference the contents of the specified source object. In order to resolve this error, we will need to implement a method that returns a similar value.

To do that, we will use the datauri package to return a data URI. To use this package, we will need to add it to our project. In the terminal, stop your server and run the following command:

Next, we will need to include the library in our server code. To do this, open server/index.js and add the following above the  const { JSDOM } = jsdom; line:

Then, in the setupAuthoritativePhaser function add the following code above the  dom.window.gameLoaded = () => { line:

Let’s review the code we just added:

  • First, we included the datauri package and we created a new instance of it.
  • Then, we created the createObjectURL and revokeObjectURL functions that are not implemented in jsdom.
  • For the revokeObjecURL function, we won’t have the function do anything.
  • For the createObjectURL function, we use the datauri.format method to format the blob into the format we need.

Now, if you save your code changes and start the server everything should start up fine.

Adding players – Client

With our server code for adding players in place, we will now work on the client side code. The first thing we need to do is load the asset that will be used for the player. In the public folder, create a new folder called assets, and in this folder copy over the spaceShips_001.png image from the other assets folder.

To load the image in our game, you will need to add the following line inside the  preload function in  public/js/game.js:

With the ship image loaded, we can now create the player in our game. Earlier, we set up Socket.IO to emit a currentPlayers event anytime a new player connects to the game, and when this event is fired we also passed a players object that contains the data on the current players.

Update the  create function in  public/js/game.js to match the following:

Let’s review the code we just added:

  • First, we created a new Phaser group which will be used to manage all of the player’s game objects on the client side.
  • We used  socket.on to listen for the  currentPlayers event, and when this event is triggered, the function we provided will be called with the  players object that we passed from our server.
  • When this function is called, we loop through each of the players and we check to see if that player’s id matches the current player’s socket id.
  • To loop through the players, we use  Object.keys() to create an array of all the keys in the Object that is passed in. With the array that is returned we use the  forEach() method to loop through each item in the array.
  • Lastly, we called the  displayPlayers() function and passed it the current player’s information, and a reference to the current scene.

Now, let’s add the  displayPlayer function to  public/js/game.js. Add the following code to the bottom of the file:

In the code above we:

  • Created our player’s ship by using the x and y coordinates that we generated in our server code.
  • We used  setOrigin() to set the origin of the game object to be in the middle of the object instead of the top left.
  • We used  setDisplaySize() to change the size and scale of the game object.
  • We used  setTint() to change the color of the ship game object, and we choose the color depending on the team that was generated when we created our player info on the server.
  • We stored the playerId so we can find the game object by that id later.
  • Lastly, we added the player’s game object to the Phaser group we created.

If you refresh your browser, you should see your player’s ship appear on the screen.

Also, if you refresh your game, you should see your ship appear in different locations, and it should randomly be red or blue.

Conclusion

With our client-side code for displaying the player in place, that brings Part 2 of this tutorial series to an end. In Part 3, we continue our multiplayer game by:

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

I hoped you enjoyed our second instalment and found it helpful. If you have any questions or suggestions on what we should cover next, please let us know in the comments below.