Create A Basic Multiplayer Game In Phaser 3 With Socket.io – Part 2

In part one of this tutorial, we created our Node.js server, set up a basic Phaser game, and added Socket.io to allow communication between the two. If you missed it, you can find part one here. In this tutorial, we are going to focus on adding the client-side code that will: add and remove players from the game, handle player input, and allow players to pick up collectibles.

If you didn’t complete part one and would like to continue from here, you can find the code for part one here.

You can see what we will be completed below:

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

Let’s get started!

Learn Phaser 3 with our newest Mini-Degree

The HTML5 Game Development Mini-Degree is now available for Pre-Order on Zenva Academy. Learn to code and make impressive games with JavaScript and Phaser 3!

Get Instant Early Access

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. 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 public 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  game.js:

With the ship image loaded, we can now create the player in our game. In part one of this tutorial, we set up the Socket.io connection to emit a currentPlayers event anytime a new player connected to the game, and in this event, we also passed a players object that contains all of the current players. We will use this event to create our player.

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

Let’s review the code we just added:

  • 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  addPlayer() function and passed it the current player’s information, and a reference to the current scene.

Now, let’s add the  addPlayer function to  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.
  • 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.
  • 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.
  • 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.

If you refresh your browser, you should see your player’s ship appear on the screen. If your server is not running, you can start it by navigating to your project folder in the command line, and run the following command:  node server.js.


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

Adding other players

Now that we have our player appearing in the game, we will add the logic for displaying other players in our game. In part one of the tutorial, we also set up Socket.io to emit a  newPlayer and a  disconnect event. We will use these two events, and our current logic for the currentPlayers event to add and remove other players from our game. To do this, update the  create function to match the following:

Let’s review the code we just added:

  • We created a new group called  otherPlayers, which will be used to manage all of the other 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.
  • We updated the function that is called when the  currentPlayers event is emitted to now call the  addOtherPlayers function when looping through the players object if that player is not the current player.
  • We used  socket.on() to listen for the  newPlayer and  disconnect events.
  • When the  newPlayer event is fired, we call the  addOtherPlayers function to add that new player to our game.
  • When the  disconnect event is fired, we take that player’s id and we remove that player’s ship from the game. We do this by calling the  getChildren() method on our  otherPlayers 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.
  • Lastly, we use the  destroy() method to remove that game object from the game.

Now, let’s add the  addOtherPlayers function to our game. Add the following code to the bottom of  game.js:

This code is very similar to the code we added in the  addPlayer() function. The main difference is that we added the other player’s game object to our  otherPlayers group.

The last thing we need to do before we can test our game logic for adding the other players is, we need to load an asset for these players. You can find that asset here.

This image will need to be placed in the  assets folder. Once the image is there, we can load this image into our game. In the  preload function, add the following line:

Now, if you refresh your game in the browser, you should still see your player’s ship. If you open another tab or browser and navigate to your game, you should see multiple sprites appear in the game window, and if you close one of those games you should see that sprite disappear from the other games.


Handling player input

With the logic for displaying all of the players in the game in place, we will move on to handling player input and allow our player to move. We will handle player input by using Phaser’s built-in keyboard manager. To do this, add the following line at the bottom of the  create function in  game.js:

This will populate the cursors object with our four main Key objects (up, down, left, and right), which will bind to those arrows on the keyboard. Then, we just need to see if these keys are being held down in the  update function.

Add the following code to the  update function in  game.js:

In the code above, we did the following:

  • We are checking if the left, right, or up keys are pressed down.
  • If the left or right key is pressed, then we update the player’s angular velocity by calling  setAngularVelocity(). The angular velocity will allow the ship to rotate left and right.
  • If neither the left or right keys are pressed, then we reset the angular velocity back to 0.
  • If the up key is pressed, then we update the ship’s velocity, otherwise, we set it to 0.
  • Lastly, if the ship goes off screen we want it to appear on the other side of the screen. We do this by calling  physics.world.wrap() and we pass it the game object we want to wrap and an offset.

If you refresh your game, you should now be able to move your ship around the screen.


Handling other player movements

Now that our player is able to move, we will move on to handling other player’s movement in our game. In order to track other player movements, and move their sprites in our game, we will need to emit a new event any time a player moves. In the  update function of  game.js add the following code inside the  if statement:

Let’s review the code we just added:

  • We created three new variables and use them to store information about the player.
  • We then check to see if the player’s rotation or position has changed by comparing these variables to the player’s previous rotation and position. If the player’s position or rotation has changed, then we emit a new event called  playerMovement and pass it the player’s information.
  • Lastly, we store the player’s current rotation and position.

Next, we will need to update the Socket.io code in  server.js to listen for the new  playerMovement event. In  server.js, add the following code below the  socket.io('disconnect') code:

When the  playerMovement event is received on the server, we update that player’s information that is stored on the server, emit a new event called  playerMoved to all other players, and in this event we pass the updated player’s information.

Lastly, we will need to update the client side code to listen for this new event, and when this event is emitted, we will need to update that player’s sprite in the game. In the  create function in  game.js add the following code:

Now, if you restart your server and open up the game in two different tabs or browsers, and move one of the players, you should see that player move in the other game.


Collecting Stars

With the game now handling other player movements, we need to give the players a goal. For this tutorial, we are going to add a star collectible to the game for the players to collect, and when they do, their team will get ten points. To do this, we will need to add a few more Socket.io events to our game, and we will start with the server logic first.

First, we will add two new variables to our server: star and scores. The  star variable will be used to store the position of our star collectible, and the  scores variable will be used to keep track of both team’s score. In  server.js , add the following code beneath the  var players = {}; line:

Next, we will emit two new events when a new player connects to the game,  starLocation, and  scoreUpdate. We will use these two events to send the new player the star collectible’s location, and the current scores of both teams. In  server.js, add the following code beneath the  socket.emit('currentPlayers', players); line:

Lastly, we are going to listen for a new event called  starCollected, which will be triggered when a player collects the star. When this event is received, we will need to update the correct team’s score, generate a new location for the star, and let each player know about the updated scores and the stars new location.

In  server.js, add the following code beneath the  socket.on('playerMovement') code:

Now that our server logic has been updated, we will need to update the client side code in  game.js. In order to display the two teams scores, we will use Phaser’s Text Game Object. In the  create function, add the following code at the bottom of the function:

Let’s review the code we just added:

  • We created two new text game objects by calling  this.add.text(). When we created these two objects, we passed the location of where the object would be placed, the default text of the object, and the font size and fill that would be used for the text object.
  • When the  scoreUpdate event is received, we then update the text of the game objects by calling the  setText() method, and we pass the team’s score to each object.

If you refresh your game, you should see each teams’ score:


Finally, the last thing we need to add is the logic for the star collectible. First, we will need to load the asset for the star collectible in the  preload function. The asset for the game can be downloaded here.

In the  preload function, add the following code:

Then, in the  create function, add the following code below the score update code we just added:

In the code above we listened for the starLocation event, and when it is received we do the following:

  • We check to see if the star object exists, and if it does, we destroy it.
  • We add a new star game object to the player’s game, and we use the information passed to the event to populate its location.
  • Lastly, we added a check to see if the player’s ship and the star object are overlapping, and if they are, then we emit the  starCollected event. By calling  physics.add.overlap, Phaser will automatically check for the overlap and run the provided function when there is an overlap.

If you refresh your game, you should now see the star collectible on the screen:


Conclusion

With the star collectible added to the game, that brings this tutorial to a close. In summary, this tutorial showed you how to create a basic multiplayer game in Phaser using Socket.io and Node.js.

I hope you enjoyed this tutorial and found it helpful. If you have any questions, or suggestions on what we should cover next, let us know in the comments below.

Published by

Scott Westover

Scott Westover is a Fullstack Developer who loves coding, writing tutorials, participating in game jams, and learning about new technologies. When he is not coding, he is either spending time with his wife and two kids or is reading a great book. Scott is looking to form connections and share his knowledge. Connect with Scott on Linkedin.

Share this article

19
Leave a Reply

avatar
11 Comment threads
8 Thread replies
8 Followers
 
Most reacted comment
Hottest comment thread
12 Comment authors
StudentEricChloeGauriDante Recent comment authors
newest oldest most voted
Arxot
Guest
Arxot

Thank you Scott! Great job!
What about collisions between ships?Is it hard to implement in Phaser multiplayer mode?

Edgardo Martinez
Guest
Edgardo Martinez

there is a bug with the overlap method?

Student
Guest
Student

Having the same issue :/ Did you manage to resolve yours?

LuisQuin
Guest
LuisQuin

As I see, in this chunk of code, you add a new collider to the world when a star is generated, and that’s definitely surplus:

this.socket.on(‘starLocation’, function (starLocation) {
if (self.star) self.star.destroy();
self.star = self.physics.add.image(starLocation.x, starLocation.y, ‘star’);
self.physics.add.overlap(self.ship, self.star, function () {
this.socket.emit(‘starCollected’);
}, null, self);
});

Am I right?

LuisQuin
Guest
LuisQuin

Regarding to my last comment. Maybe this could be a better solution? self.socket.on(“starLocation”, function(starLocation) { if (!self.star) { self.star = self.physics.add.image(starLocation.x, starLocation.y, “star”); self.physics.add.overlap(self.ship, self.star, function dummyCollectStar() { this.socket.emit(“starCollected”); }, null, self); } else { self.star.x = starLocation.x; self.star.y = starLocation.y; } });

Student
Guest
Student

Hi, I have tried both solutions and been unable to get the collision to work. Any ides on how I could resolve this?

Edgardo Martinez
Guest
Edgardo Martinez

how to add an input field on phaser 3? Can you make a tutorial?

coder
Guest
coder

Hi, this works fine if all clients are on localhost, but as soon as you try to connect to other machines you get problems with CORS and firewalls and more.
Could you do a tutorial on actually using the server for online games?

Pablo Farias Navarro
Admin

To solve the CORS issue on a live server add this to preload:

this.load.crossOrigin = “anonymous”;
this.load.setBaseURL(“https://example.com”);

coder
Guest
coder

Do you not have to enable it on the server, too? I was told to set allowedOrigins to “*:*”.

And changing the base URL messes with any other this.load uses, doesn’t it?

hiteangry
Guest
hiteangry

Hello,
great tutorial what about lag compensation for update position stay smooth when one enemy lag ?

Mike
Guest
Mike

At a very basic level, what would you do to prevent cheating? Like if a client sends some manipulated data through the socket that gives them an advantage. Any tips?

Dante
Guest
Dante

Some tips for anti cheating:
You shouldn’t send postion of player to server, just send value of keypress.
Ex: Player press [Up] => send server player.pressUp = true => player.position.x += ?? & player.position.y += ??, then send new postion to all other player.
All calculator about game’s logic should be code in server file.

Gauri
Guest
Gauri

It’s really an awesome tutorial. I have a question regarding this can we implement the same server for IOS on client side??
I will we thankful for the answer.

Chloe
Guest
Chloe

Hi, great tutorial! Only problem is – the movement doesn’t seem to work for me. When adding the code into the update function, it stops displaying any players on the screen. Code is like for like to yours! I can’t seem to see where I am going wrong. Any suggestions?

Eric
Guest
Eric

Hello, when adding the following line : this.physics.world.wrap(this.ship, 5);, the following error is generated at runtime: “this.physics.world.wrap is not a function.” What should I be using instead?

Student
Guest
Student

I commented this line out and it works but it doesn’t stop the player moving outwith the game board 🙂