How to Build a Realtime Multiplayer Game in JavaScript with PubNub

Oscar Castro
ITNEXT
Published in
12 min readMar 19, 2019

--

“Gamer don’t die. They respawn!”

Scenario: You want to build an online multiplayer game. This is your first time doing so and you are excited! But how do you achieve realtime interaction among players? After some quick research you find that you can use socket.io. You look online and find a tutorial on how to implement socket.io. You will need to install Node.js and Express, set up the Express server, add the socket.io code in the same file as the Express code, and finally connect the client to the server. Whew, all of that just to set up! Also, since this is your first time implementing realtime infrastructure to your game, you will need to learn some networking concepts, such as client prediction. The time spent doing all of this is time you could have spent developing your game!

The truth is that implementing realtime capabilities to your application is not an easy thing to do. There are a lot of great tools you can use, but you may encounter several problems along the way, especially when you start scaling user growth. This is where PubNub comes in. PubNub offers realtime infrastructure that gives users a secure and reliable connection to connect their devices and deliver data using PubNub’s global data streaming network. This is done with a 0.25 seconds global omni-directional message delivery rate.

There are endless applications that can be done using PubNub’s API, such as chat apps, rideshare apps, retail apps, multiplayer games, etc. In the case of multiplayer games, PubNub facilitates realtime interaction among player’s and powers the gaming functionality and social features that creates a smooth gaming experience.

For this tutorial, PubNub will be used to connect two players in a game where they can interact and chat with each other. The game UI will consist of a canvas for drawing and two chat boxes, one for writing the guessed word and the other for players to message each other. This game will be built in JavaScript using PubNub’s JavaScript V4 SDK and ChatEngine JavaScript SDK.

Game overview

In this tutorial, we’re going to build a drawing and guessing game from scratch. In the game, one player draws a word and the other player guesses the word based on the drawing. Player is awarded a point for guessing the correct word. The player with the most points wins.

The entire project can be found on my GitHub repository. Here is a screenshot of the game we’re going to build:

Setup PubNub

Sign up for a free PubNub account here. Once you sign up, you will be taken to the admin page where you can get your Publish/Subscribe keys. You can get the keys from the “Demo Project App”, or you can create a new app to get a new set of keys. Copy and paste the keys into a new file and label the keys as “game keys” (Note: This file is used to temporarily store your keys and won’t be used for the game).

We’re going to be using presence to detect players in the lobby and the game channel. To enable channel presence, click on the app you got the keys from, click on the Demo Keyset box, and go down to Application add-ons. Look for presence and click the switch to turn it on. Check the box for Generate Leave on TCP FIN or RST and for Global Here Now.

In order for players to chat in realtime, we’re going to use PubNub’s ChatEngine. To set up ChatEngine, you will need to use different Publish/Subscribe keys from the keys used for the game. You can get your free pre-configured keys here. Add those keys to the same file you used above and label the keys as “main chat keys”. Since the game uses two separate chat boxes (one for messaging and the other for guessing the word), you will need to reload the page (or click here) to get different keys. Add those keys to the file and label them as “guess word chat keys”.

Now that we got the above done, we can start working on the game. Create a directory to store the game files. You can use any text editor for this tutorial. I personally like using VS Code with the Live Server Extension.

Setup the HTML/CSS files

Create a new file, index.html, and add it to the directory you created for the game. We will set up the canvas, the two containers for the chat boxes, and the color swatch. Make sure that for your external files, you include PubNub’s JavaScript SDK and ChatEngine JavaScript SDK.

That’s it for the html file! You can get the css file from here.

Setup the lobby

Create a new file, lobby.js, and add it to your game directory.

The code for the lobby will be inside an anonymous function.

(function() {
// Lobby code goes here
})();

We first initialize the player variable.

const player = {
name: '',
sign: '',
score: 0
}

The player variable contains 3 object properties:

1) name: empty at first but later set to ‘Host’ or ‘Guest’.
2) sign : empty at first but later set to ‘H’ or ‘G’.
3) score : set to 0.

Since we’re going to be changing the text, we need to get the elements from the html file. We do so like this:

function $(id) { 
return document.getElementById(id);
}
let score = $('score'), triesLeft = $('triesLeft'), guessWord = $('guessWord'), opponentScore = $('opponentScore');

At the start of a new game, the player will enter a lobby name.

let lobby = prompt("Enter name of lobby");
let game = lobby; // game is the channel where the game takes places
lobby = lobby + 'Lobby'; // separate channel for lobby

We set game to the value of lobby and append ‘Lobby’ to lobby. We do this to separately get the presence of the players from the two channels.

Let’s initialize the variables that will be used in this file.

const newUUID = PubNub.generateUUID();
let isHost = false;
let ChatEngine = '';
let GuessWordChatEngine = '';

Instantiate a new PubNub instance and replace ‘game_pub_key’ and ‘game_sub_key’ with your “game keys”.

const pubnubGuessGame = new PubNub({
uuid: newUUID,
publish_key: 'game_pub_key',
subscribe_key: 'game_sub_key',
ssl: true
});

Create a new listener to be notified of connectivity status and presence. Also, add the listener.

Let’s go over the code. There are two callback functions in the listener: presence and status. Presence detects new players in the lobby channel. The first player to enter a new lobby becomes the host and the second player to enter that same lobby becomes the guest. When two players are in a lobby, the listener will be removed, the players will be unsubscribed from the lobby channel (but still be subscribed to the game channel), and the game will start.

Since the players are no longer subscribed to the lobby channel, other players will have access to that lobby since it’s empty. To prevent this issue, we make a call to hereNow() to check the number of occupants in the game channel. If two players are in the game channel, then we notify the new player that a game is in progress, remove the listener, and unsubscribe the new player.

Inside the presence callback function, we connect the players to the ChatEngine by make a call to connectToChat().

Replace ‘main_chat_pub_key’ and ‘main_chat_sub_key’ with your ‘main chat keys’. Replace ‘guess_word_chat_pub_key’ and ‘guess_word_chat_sub_key’ with your ‘guess word chat keys’. The uuid’s for the players will either be ‘Host’ if they are the host and ‘Guest’ if they are the guest. This is how it will appear when they message each other, which they can start doing once the game starts.

When a connection has been established, event.category, in the status callback function, returns ‘PNConnectedCategory’. When this happens, a call to setUpCanvas() is made.

Right below the listener we just implemented, subscribe to the lobby channel and enable presence.

pubnubGuessGame.subscribe({
channels: [lobby],
withPresence: true
});

Now that we finished the lobby, we can start writing code for the game itself.

Implement the game

Create a new file, guessDrawing.js, and add it to your game directory.

The code for the game will be inside the function gameStart(). The parameters are variables from the lobby.js file.

function gameStart(pubnubGuessGame, ChatEngine, GuessWordChatEngine, game, player){
// Game code goes here
}

Initialize the variables that will be used for the game.

A random element will be chosen from the array words and be removed from the array. The chosen word will only be displayed to the player drawing the word. The first player to draw is the host and the guest will have 3 tries to guess the word.

Again, we need access to the text from the html file.

function $(id){ 
return document.getElementById(id);
}
let chatLog = $('chatLog'), chatInput = $('chatInput'), guessWordChatLog = $('guessWordChatLog'), guessWordInput = $('guessWordChatInput'), clearCanvasButton = $('clearCanvasButton'), score = $('score'), colorSwatch = $('colorSwatch'), triesLeft = $('triesLeft'), guessWord = $('guessWord'), opponentScore = $('opponentScore');

When the ChatEngine successfully connects, a ‘$.ready’ event is fired. Since we already connected the ChatEngine in the lobby.js file, we need to write the code for the ‘$.ready’ event.

ChatEngine.on('$.ready', function(data){
// Every time a message is recieved from PubNub, render it.
ChatEngine.global.on('message', onMessage);
});
GuessWordChatEngine.on('$.ready', function(data){
GuessWordChatEngine.global.on('message', onMessageGuessWord);
});

The ChatEngine is all set up and ready to use. Anytime a player sends a message in the main chat box, sendMessage() will be called.

The listener calls sendMessage() when a key is pressed. Inside the function, we check if the key pressed was ‘enter’, or Key Code 13, and if the input is greater than 0. If so, the text the player wrote, as well as their assigned name (Host or Guest), is sent. The chat input is set to an empty string so the player can write a new sentence.

When a message is received, onMessage() is called to add the message to the main chat log.

We will implement the functions for the guess word chat box later on as we need to first set up the game. For now, let’s create a new listener, ‘gameListener’, and add the listener.

We add a new callback function, message, which listens for any messages published to the game channel. The channel will receive several messages, but we only need to take care of 3 specific messages:

1) msg.message. chosenWord: Whenever a random word is chosen from the array, we publish that word to the channel and add it to value of the variable chosenWord. We only do this for the player whose turn it is to guess the drawing.

2) msg.message.plots: As soon as a player stops drawing on the canvas, we add the path of the drawing to the plots array and publish that array, along with the color of the drawing, to the channel. The other player will receive this message and make a call to drawFromStream(), which makes a call to drawOnCanvas() to draw on their canvas.

Make sure to initialize the variables before drawFromStream().

3) msg.message.clearTheCanvas — When the player drawing presses the clear button, or on a new round, a message is published to clear the canvas for both players. When the message is received, clearCanvas() is called.

function clearCanvas(){
ctx.fillStyle = 'WHITE';
ctx.clearRect(0,0,window.innerWidth, window.innerHeight);
ctx.fillRect(20,20,window.innerWidth, window.innerHeight);
}

For the presence callback function, we need to check if a player leaves the game before it’s over. If so, then response.action is ‘leave’ and the other player is announced the winner. Both players are then unsubscribed from the game channel and disconnected from the ChatEngine. Players also unsubscribe and disconnect when the game is over.

When a connection has been established, status is called. If event.category returns ‘PNConnectedCategory’, then the game starts. Before we implement startGame(), let’s subscribe to the channel.

pubnubGuessGame.subscribe({
channels: [game],
withPresence: true
});

Below this we add a function, publish(), which is called when we need to publish data to the channel. To publish, we need to pass in the name of the channel and the data we want to publish.

function publish(data){
pubnubGuessGame.publish({
channel: game,
message: data
});
}

Now let’s implement startGame().

The player whose turn it is to guess the drawing will be returned. The other player will be shown a random word from the array and that word will be published to the channel. The player will then get access, through the listeners, to the color swatch, the clear button, and the drawing functions. There are 3 drawing functions:

1) startDraw(): When the player presses down on the canvas, this function is called to set the variable isActive to true, which means the player is ready to draw.

function startDraw(e) {
e.preventDefault();
isActive = true;
}

2) draw(): As soon as the player starts to move on the canvas, this function is called. The canvas coordinates which displays the path is pushed to the array plots and sent, along with the color, to drawOnCanvas(). Since this game can be played on touch screens, the coordinates are rounded.

3) endDraw(): Once the player stops moving on the canvas, this function is called. Variable isActive is set to false again, color and plots is published to the channel, and the plots array is set to an empty array.

When the player drawing presses the clear button, a call to clearButton() is made to clear the canvas. Variable clearTheCanvas is set to true and is published to the channel.

function clearButton(e){
e.preventDefault();
clearTheCanvas = true;
publish({
clearTheCanvas: clearTheCanvas
})
}

Once the player guesses the correct word for the drawing, or runs out of tries, roles are switched. Players keep switching roles until someone reaches a score of 3. When players switch roles, nextRound() is called.

First, the function checks if a player has a score of 3, if so, the winner is announced and a call to unsubscribeFromGame() is made. Else, the canvas is cleared for both players, the guess word chat log is cleared, and tries is reset to 3. If it’s not a player’s turn to draw, then guessWord and triesLeft is set to the appropriate text and the listeners for drawing, along with the clear button, is removed so they won’t have access to the canvas. The player drawing will have guessWord and triesLeft set to an empty string and a call to startGame() is made.

The only thing left to implement are the functions for the guess word chat box. Let’s implement onMessageGuessWord().

Whenever a message is received, we display it on the guess word chat log. This message should be the guessed word. We check if it matches with the word chosen for that round. If the word matches and tries is greater than 0, then we award the player that guessed the correct word a point. We increment that players score and display it on their screen as ‘My Score: #’. The updated score is showed to the other player as ‘Opponent’s Score: #’. A call to switchTurns() is made after to switch the roles of the players and to start a new round.

function switchTurns(){
turn = (turn === 'H') ? 'G' : 'H';
nextRound();
}

If the player runs out of tries, no point is awarded to them and roles are switched. Else, we decrement the number of tries left.

Now that we took care of receiving messages from the guess word chat box, let’s add a new keypress listener and implement the function sendMessageGuessWord().

This is almost the same as sendMessage(), except we add an if statement to check that only the player guessing the drawing can send a message. The player drawing does not have permission to send a message and is alerted if they attempt to send a message. This is done as an edge case, since in most cases the player drawing will not send the word they are drawing. After all, it’s not in their best interest to do so.

That’s all! In total you should have 4 files: index.html, style.css, lobby.js and guessDrawing.js. If you’re using VS Code with the Live Server Extension, run the live server and enter a lobby name. If you’re using another text editor, run the index.html file. Copy and paste the url to another tab, or preferably a new window, enter the same lobby name, and the game will start!

How to Play

1) First player in the lobby is the host. Second player that joins that same lobby is the guest.

2) Game starts when there are two players in a lobby.

3) The host is given a word and needs to draw it on the canvas.

4) The guest has 3 tries to guess the word that represents the drawing and enter their guess in the guess chat box. If the guest guesses the correct word, they are awarded a point.
— Note: While the guest can see the drawing on their canvas, they cannot draw on the canvas until it’s their turn to draw a word.

5) Once a word is guessed, or tries equals to 0, roles are switched, and the guest draws a new word while the host guesses the drawing.

6) Roles keep switching until a player reaches a score of 3. That player is announced the winner and the game ends.
— If a player leaves before the game ends, the other player is announced the winner.

Final Note

Game is kept simple, as the purpose of this game is to demonstrate how PubNub’s API is implemented in a multiplayer game. The possibilities for this game are endless. You can allow for more players in a lobby, let players choose a category to draw (like animals), increase the winning score from 3, add more colors to choose from, add a time limit for drawing the word, etc.

I hope you enjoyed this tutorial!

Acknowledgements:

Inspiration for the game, and some pieces of code, was used from PubNub’s opensource project: codoodler - a multi-user doodling web app.

Other sources used include: Building Your First Multiplayer Game and Adding In-game Chat to a Multiplayer Game with ChatEngine

--

--