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

srushtika

Srushtika Neelakantam

Posted on June 12, 2020

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 :)

💖 💪 🙅 🚩
srushtika
Srushtika Neelakantam

Posted on June 12, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related