DEV Community

Cover image for Building a realtime multiplayer browser game in less than a day - Part 4/4
Srushtika Neelakantam for Ably

Posted on • Edited on

Building a realtime multiplayer browser game in less than a day - Part 4/4

Hello, and welcome to the final part of this article series where we are looking at the step-by-step implementation of a realtime multiplayer game of space invaders with Phaser3 and Ably Realtime. 🚀


Here's the full index of all the articles in this series for context:


In this article, we'll finish up the client-side code to render the game and also add the home and leaderboard screens for our game.

If you recall, in the first article we added the GameScene class and defined the preload() method in it. We also added the create() and update() methods but didn't define them fully.

Let's start by adding some variables that we'll use later. Add these at the top of script.js (which should be inside the public folder:

Make sure to update the BASE_SERVER_URL with your server's URL. If you have hosted the game locally, this URL would be your localhost with the port number.

Next, we'll have the client connect to Ably and subscribe to the channels. To do that, go to the Ably dashboard and add the following code, right below the variable declarations in script.js

One of the key things to note here is the gameRoom.presence.enter(myNickname); method. Ably uses a concept called Presence to determine connected clients in an app. It fires an event whenever a new client joins, or when an existing client leaves or updates their data.

Notice that this is where we instantiate a new game object with the GameScene that we started defining in the first part. So, let's resume that. The create() method of the class should now look as follows:

We had already defined the this.anims.create()method in the first article. Just above that, we add and initialize a few variables. We then subscribe to the game-state and game-over events on the gameRoom channels.

When we get a game-state update, we update the client-side variables as per the latest info from the server.

When we get a game-over update, we store the leaderboard info in local storage. We then unsubscribe the client from all channels and simply switch to a new webpage because either someone won or all players became dead.

Let's look at the update() method next:

In the update method, we move the existing game objects in accordance with the latest info. We also create new avatars for the newly joined players, and kill avatars of any player that has died by calling the explodeAndKill() method. We also update the score, and flash the join and leave updates in the <p> elements outside the game canvas.

If the server says a player just died, we call the explodeAndKill() method that will perform the explode animation and destroy that player's avatar.

A bullet gets fired once for every five game ticks. So the server either sends a blank or a bullet object, with a unique ID and a position that matches the ship's y-axis level. If it hasn't already been shot, its toLaunch flag will be true. So we check that and create a new bullet by calling the createBullet() method. Otherwise, we'll move an existing one.

We also check if the player has pressed the left of right keys via the publishMyInput() method.

Let's define these methods next. Please note that these methods are part of the GameScene class.

In the createBullet() method, we add a new bullet object according to the latest position of the ship and add this bullet to the visibleBullets associative array that's part of the GameScene class. We also add an overlap method for the current player's avatar and every bullet we add. This method will keep track of the overlap of the two game objects overlapping. When that occurs, Phaser will invoke a callback method, which in this case is publishMyDeathNews(). We'll define that later.

In the publishMyInput() method, we check if the left or right key was pressed, and if yes, publish that info to Ably. It's worth noting here that we never move the avatars directly as a result of user input. We publish this info to the server, which in turn fans it out to all the players, including the current player, resulting in a perfect state synchronisation. This communication happens so fast that it doesn't really feel any different to the user playing the game.

In the explodeAndKill() method, we create a new instance of the Explosion class. We haven't defined that yet, so let's take a brief detour from the script.js file that we've been working on, to add it. Create a new file in the public folder, call it explosion.js and paste the following code in it.

This class extends Phaser.GameObjects.Sprite and plays the explode animation that we defined in the create() method of our GameScene class in the script.js file.

Now let's get back to script.js and define one last method within the GameScene class, the publishMyDeathNews():

This method is invoked when a bullet object overlaps with the current player's avatar, meaning the player has been shot. When that happens, we simply publish this information to the server so it can update the game state accordingly and fan this information out to all the clients, including the current player, so they can update their respective game states accordingly.

We are all done with the game implementation. We just have to add the home and leaderboard pages to make the game more complete.

Adding the home and leaderboard pages

In the views folder, add four files:

  • gameRoomFull.html
  • intro.html
  • winner.html
  • gameover.html

Homepage screen

Winner screen

In the public folder, add three files:

  • nickname.js
  • winner.js
  • gameover.js

The gameRoomFull.html is displayed when anyone tries to join the game after the preset maximum number of players have already joined.

The intro.html file gives the user a simple text box to enter their nickname. This info is used to flash join/leave updates and also show the info in the leaderboard.

The winner.html page is shown if the game ends due to a player winning the game. This page will then display their nickname as the winner and also show the first and second runners up.

The gameover.html page is shown if all the players in the game die. This page just shows the nicknames of the top two scorers.

The related JavaScript files simply retrieve the info from the local storage and set it in the relevant HTML elements.


That's it, we've now full implemented the game 🙌🏽🙌🏽🙌🏽

Let's go ahead and run it. We first need to run the server, so from your command line, navigate to the folder where the server file is and run node server.js. This will start the server. Now, open three browser windows and keep them side-by-side. Hit the base URL of your server from all the three windows. You should see the intro.html page being served asking for a nickname. Give each player a nickname and enter. After the third player enters, the ship starts with the bullets going off. Make sure to control each player to avoid getting killed.

If it's running as expected, you can host this game using a free hosting service such as Heroku or Glitch. This will allow you to access the game via a public URL letting you play the game for real with your friends on other computers.

A separate release relevant to this tutorial is available on GitHub if you'd like to check it out.

You can also follow the Github project for latest developments on this project.


As always, if you have any questions, please feel free to reach out to me on Twitter @Srushtika. My DMs are open :)

Top comments (16)

Collapse
 
vmx profile image
Mohsen Tabatabaie

ive got error while trying to run the server - at Module._resolveFilename (node:internal/modules/cjs/loader:1248:15)
at Module._load (node:internal/modules/cjs/loader:1074:27)
at TracingChannel.traceSync (node:diagnostics_channel:315:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
at Module.require (node:internal/modules/cjs/loader:1339:12)
at require (node:internal/modules/helpers:126:16)
at Object. (C:\Users\mosit\Desktop\multiplayer live game\public\server.js:1:19)
at Module._compile (node:internal/modules/cjs/loader:1546:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1691:10)
at Module.load (node:internal/modules/cjs/loader:1317:32) {
code: 'MODULE_NOT_FOUND',
requireStack: [
'C:\Users\mosit\Desktop\multiplayer live game\public\server.js'
]

Collapse
 
vmx profile image
Mohsen Tabatabaie

(C:\Users\mosit\Desktop\multiplayer live game\public*server.js:1:19)*

Collapse
 
vmx profile image
Mohsen Tabatabaie

where is dotenv ?

Collapse
 
arttukuikka profile image
Arttu Kuikka

Hello i am getting this error(imgur.com/a/jLGwZwg even thought i have added the ably key

Collapse
 
srushtika profile image
Srushtika Neelakantam

Hi, it's expecting an authUrl, not a direct API key. If you'd like to use the API key directly instead to authenticate (which is not recommended btw for the frontend clients) - you'll need to replace the key authUrl with key in the init method. Here's an example: ably.com/documentation/core-featur....

If you are still facing issues, please feel free to open a support ticket at ably.com/contact and the team can help out.

Collapse
 
awesomepants profile image
Awesomepants • Edited

Hi!
I'm a little confused on how to do this step: "Hit the base URL of your server from all the three windows."
How do I find the url of the local file? (Thanks in advance!)
Edit: I figured this out, and I'll leave this here for reference, in case anyone else doesn't know how to do this. (I am relatively new to web development)
It's fairly simple. You just need to open your browser and type in localhost:5000 (make sure your server is running first!)
For some reason I thought I had to input the file path.

Collapse
 
brickfoe profile image
BrickFoe

Can I add my own assets/animations to make the game more unique?

Collapse
 
srushtika profile image
Srushtika Neelakantam

Yes, of course! Just replace the ones in the project. Do you have any specific questions on that?

Collapse
 
brickfoe profile image
BrickFoe

You use cdn links for the assets. Can i just add my own png images all willy nilly or do i have to do something else

Thread Thread
 
srushtika profile image
Srushtika Neelakantam

Replacing the links should do it!

Collapse
 
anibalardid profile image
Anibal

Hi ! Amazing !! Do you have a demo url ?

Collapse
 
srushtika profile image
Srushtika Neelakantam

Hey! I've temporarily hosted the demo at go.ably.io/play-space :)

Collapse
 
mdx77 profile image
Nicolas

Hello, thanks for sharing your work. I have a server hosting all the files. How can I code to have server.js run?

Collapse
 
alankritjoshi profile image
Alankrit Joshi

This is great. I was working on building a mutliplayer game with Phaser and my Go server. I will have to figure out an alternative for P2.js or patch it in somehow.

Collapse
 
angel17 profile image
Angel Superstore

I enjoy reading your post. This is so informative! ornamental iron works

Collapse
 
vmx profile image
Mohsen Tabatabaie

very bad instruction
what you mean by " go to ably dashbord and add this code to script.js !!! "