Setting up a Multiplayer Game with Socket.io

z-f2VkDQ.jpg

 

Part 1: Introduction

 

Preferred Prerequisites

In this tutorial, we will be focusing on how to set up a server and connect your game to it so you can enable multiplayer functions for your project via Socket.io behaviours. We will be covering advanced features and behaviours in hyperPad and it is strongly recommended to review some other tutorials first and have a good feel for the software before continuing.

This tutorial assumes you have a good understanding of hyperPad's core functionality as well as a minimal understanding of written programming, as we'll be focusing on Javascript scripting for building a game server. This tutorial will also assume some basic understanding of networking and the differences between a server and its clients.

The primary goal of this tutorial is to teach you what you need to do to add multiplayer functionality into your game; not to create the game itself. We will explore the relationship between Socket.io clients and the server, and how they communicate with each other with the Socket.io behaviours.

 

Requirements

To create and host the server, you will need a computer able to run Node.JS (Mac, Windows, etc). Installation of Node.JS and setting it up for our purpose will be covered in this tutorial. If you are hosting a server in your home network, you will likely need to change your gateway's port-forward settings if you wish to have incoming connections from outside your local network.

For this tutorial, we have already created a simple demo game. In it, we have two players playing a game of tag in a small maze. We will be exploring how the Socket.io behaviours are used in the example project, so it is recommended you download and open the project in hyperPad.

The completed hyperPad example project can be downloaded here: Multiplayer tag tutorial.tap

 

Overview

In this tutorial, we will go over the basics of creating a server for your game.  The server will handle most of the details in the game such as scoring, game lobbies, etc. Then, we will create behaviours that send information from our game to the server and vice-versa.

Here is the general flow of our game:

1. From the main menu, we will connect to our server and let the user decide to either create or join a game.

        a. If creating a game, load the waiting room.

        b. If joining a game, load a list of available game rooms.

        c. When two players are in a waiting room, the game launches.

2. In a game, the server randomly place the players in one of four areas and assign one of them to be "It".

        a. Tagging the other player will randomize their locations, swap the It status and add a point to the player who tagged the other.

        b. Should no tags happen within a certain period of time, the server will remove 1 point from the player who is currently It and swap the It status before randomly placing players in the spawn zones again.

3. Once a player has reached a certain number of points, it will load an overlay declaring a winner. Then, the players will disconnect from the room and return to the main menu.

 

Part 2: Setting Up the Server

 

Creating a Socket.io server requires us to run a Javascript application using the Socket.io library, listening for player connections. Socket.io is a Javascript networking library that simplifies a lot of the under-workings of building a networked application for us. More details to follow.

 

Download and Install Node.JS

To get started, head over to Node.JS and download it on to the computer (Mac, Windows, etc.) you wish to host the server on. When finished downloading, run the installer and follow its instructions. All options during installation can be left default. Node.JS gives us the ability to run Javascript applications on their own without the need of a web browser.

 

Downloading and Running the Example Server

Next, download this tutorial's Multiplayer Server Example on GitHub:

https://github.com/hyperPad/multiplayerServerExample

Click the "Clone or download" button and select "Download ZIP". This will download a copy of the code for the server example.

chrome_2019-08-05_14-22-14.png

Extract the downloaded ZIP. Inside you will find a few small files, but the most notable file here is the "index.js" file which is the code for our server. Next, open your command line/terminal inside the server example folder.

Type "npm install" and hit enter, then let it run. npm is a package manager that reads the package.json file and downloads the necessary packages for our server. (Including Socket.io!)

cmd_2019-08-05_14-55-19.png

When the command is finished, we have everything we need to run the server. In the terminal, type "node ." and the server will start.

cmd_2019-08-05_15-02-38.png

That's it! Our server is now listening on port 3000 for incoming Socket.io connections.

"node ." launches Node.JS for the current directory, where it will look for the directory index file and run it. By default, this is the "index.js" Javascript file, which the bulk of our server's code is located and will be analyzed throughout this tutorial.

Leave the terminal open, as closing it will also close the server. You will see it print out messages when players connect or disconnect, and when rooms are created or destroyed, and other events.

Note: For allowing incoming connections from outside a home network, you will likely need to open port 3000 on your home gateway. This process varies from network to network, but a guide can be usually be found by doing an internet search for a port-forwarding guide for your home's modem/router. You may need to close and restart the Node.JS server when you change port-forwarding settings.

 

Part 3: Connecting to the Server

 

Once you have the basics of the server started, we can now connect to it in a hyperPad game. This should be set up to be the first thing that happens in your project regardless of which scene you are on (since everything needs to communicate with the server). It is best to attach this behaviour to an object on the Global Layer so it will apply across all scenes.

 

Untitled.jpg

In the example project, the Global Layer "Server" label contains the above. (The download for the project can be found in Part 1 of this tutorial under the Requirements section.)

These two behaviours are really all you need to connect to the server. First off, under the custom tab, grab the Socket.io Client behaviour and drop it down. Here, you will input your server's URL under the URL tab in its properties window. In the above picture, our server's URL was at "http://192.168.0.191:3000", including protocol and port. You will want to change this to match your server URL or it will likely not work when you start the game.

Now we have the server info we want, but we still need to connect to it. So, all we need is the Connect to Socket behaviour. Drop one down, open its properties and select the client in the empty box and set the Function property to "Connect".

Now, when our project loads up, we will automatically connect to our server.

mceclip1.png

 

Part 4: Creating and Joining Rooms

 

The next step is to create our game lobbies where players can join in and play together.

 

mceclip2.png

Here is our simple Main Menu screen. All one needs to do is tap one of the buttons to either start a room or look for available rooms.

 

Creating Rooms

Let's check how we go about communicating with the server to create our room.

 

mceclip3.png

Rather simple isn't it? So to go over this quickly, once we touch our button we will be prompted to type in a room name. Once that's done, that name is emitted to the server where the room is created and we load up the waiting room scene.

From here on, we will be using Emit to Socket often. That is because it is our main way of sending data to the server. Think of it simply as the "online version" of Broadcast Message and Receive Message (Emit to Socket performs both actions at once).

 

Now, we need to add some information to our server so it will create the room once it receives the message from our Emit behaviour.

mceclip4.png

socket.on('createRoom', (roomName, callback) => {

This line creates a socket event (as a lambda expression) on the server, listening for Emits with the event 'createRoom'.

The first parameter is the value we pass through the Emit to Socket behaviour, in this case the room name the user typed in. Here, we've named that parameter 'roomName'.

The second parameter is a function that we call later on in the socket event to signal the client. The Emit to Socket behaviour will only continue execution if the callback function is called on the server. Here, we've named that parameter 'callback'.

const room = {
id: uuid(),
name: roomName,
sockets: []
};

This will create an instance of a structure that will contain essential information about a room.

'id' will be a unique identifier generated by the utility function 'uuid()'. This will help us identify specifically this room against a list of many other created rooms later on.

'name' will be set to the name the user typed in earlier.

'sockets' will be initialized as an empty array. Later on, this will keep track of the player sockets connected to that room.

rooms[room.id] = room;

'rooms' is a global list of rooms that are currently active. Since we are creating a new room, we will store it in the list by its ID.

joinRoom(socket, room);

This will call the global function 'joinRoom' (line 29), which will add the player socket to the room's 'sockets' array. Since the player created the room, this will also make them join it, too.

callback();
});

Lastly, we invoke the callback function so the client is notified that the room is created. This will allow Emit to Socket to continue execution, which will load the Waiting Room scene next. This also marks the end of the 'createRoom' socket event.

 

Joining Rooms

Joining rooms is a little different as we will need to access the information from a different scene from the button.

 

mceclip5.png

For join rooms, we have placed the beginning of our behaviours on our global layer alongside where we connect to the server. This way we can access the information from any of our scenes, in this case our room list. For the button, tapping it will simply load the player into the room list scene.

Here we have our second behaviour for communicating with the server. Socket Event can be thought of as Receive Message since it will only activate once the set message has been broadcasted from the server.

The best way to think when using Socket Event and Emit to Socket is that Socket Event reacts only to information coming from the server, whereas Emit to Socket is called in reaction to things done locally.

As for our logic, once we connect to the server, we will Emit the 'getRoomNames' request to get any available room names.

mceclip19.png

Then, we would set a label that the player can tap on to enter.

 

mceclip6.png

This is our Room List scene. Here is where any available game rooms will be loaded and displayed. By simply tapping the room name will load the player in. Thanks to our previous behaviours, the list will automatically load any open rooms and spawn the room name labels on screen. Should nothing show up, we have our Refresh List button that simply repeats the behaviours to load them once more.

 

mceclip7.pngmceclip8.png

Here we have the logic for setting up the list. We won't go into too much detail here because we only want to know how this connects with our server.

To start, we have our Emit to Socket behaviour. This calls for the server to send information on available rooms. From there, we have a Get Array Value behaviour. All data that comes from the server will be sent as an Array and the information needed will be on the first values. So, we set our Get Array Value to get the value at index 0. From there, our behaviours will extract that data and create a label for each room. spawning them on our screen.

Next, we are going to check the object that we need to tap, in our scene it is the label called Room Name.

mceclip9.png

This text act as our button when spawned in, but we still need to attach the room ID that we want to connect to. To do that, we first need to grab the room ID, and we do that with the Get Attribute behaviour and set it to dynamic. Then, we emit to the server that we want to join this room and load into the waiting room.

mceclip10.png

socket.on('joinRoom', (roomId, callback) => {

This is the entry point for the socket event 'joinRoom'. The first parameter's value would be the room ID we want to join that was emitted to the server. Here, we named the parameter 'roomId'.

const room = rooms[roomId];
joinRoom(socket, room);

Using the roomId given by the client, we can find the correct room instance on the server. With that, we invoke the global function 'joinRoom' (line 29) with the socket of the player wanting to connect, and the room instance itself. We'll take a look at the 'joinRoom' function in just a bit.

callback();
});

Lastly, we invoke the callback function to notify the client to continue loading the Waiting Room scene, marking the end of the 'joinRoom' socket event.

So, what exactly is going on in the 'joinRoom' function? Let's take a look.

mceclip11.png

room.socket.push(socket);

As mentioned earlier, the 'room.socket' member array keeps track of the sockets connected in a room. This line does just that by pushing the socket into the array.

socket.join(room.id, () => {
socket.roomId = room.id;
console.log(socket.id, "Joined", room.id);
});

This is the official call to connect the client to a room. First we tell the socket to join a room by its ID. When that's completed the following callback is invoked, where we attach the room ID to the socket. Lastly, we log in the console that a player has joined a room!

mceclip12.png

 

The Waiting Room

The waiting room is simply a scene that we load the players into while they wait for other players to join, or for a game to begin.

mceclip13.png

Upon entering the room, we will emit a 'ready' message to the server. Once the server has received two of these, it will send a message that it is starting the game ('initGame'). We pick up that message with our Socket Event and thus, load our game level. For our behaviours, that's about it. You can add in a button that will disconnect you from the room and send you back to the main menu if you like.

On the server end, let's analyze the code to see what's going on there.

mceclip14.png

This is the 'ready' event that is called when a client has joined a room and is ready to connect.

const room = rooms[socket.roomId];

Since we have attached the room ID to the socket, we're able to get the room to check if the game is able to start.

if (room.sockets.length == 2) {

Here we check to see if there are now two players waiting in the room. Let's say that's now true, and we continue to start the game.

for (const client of room.sockets) {
client.emit('initGame');
}

Now that there are two players, we iterate through each socket and emit the 'initGame' event so each client loads the Level scene, as shown earlier.

 

Part 5: Gameplay

 

Now, to get into the meat of things. This is where 90% of our work will go. Below is our game level we designed for this tutorial.

mceclip15.png

 

Before we get into that however, we have a label titled "Game Logic", let's open that up and take a look.

mceclip16.png

Wow, that's a lot of behaviours! Don't worry though, this is simply how we spawn our players into the game. Let's take a closer look;

mceclip17.png

We start with an Emit to Socket behaviour that tells the server our game has started, with the event 'startGame'. We then grab the array the server returns, take it's first value and with the Get Dictionary Value, we grab the various attributes our object will need. A separate tree does the same, except for the opponent player. Lastly, we broadcast the message 'init' to our player object to get things started.

Let's see what's going on at the server side of things when we emit the 'startGame' event.

mceclip20.png

The first half of this socket event you see here is setting some initial values on each client, then adding any client that is not the emitting client to the local 'others' array.

mceclip21.png

In the second half, a local dictionary list called 'ack' is created. In it, we have info about ourself, and the other client(s). We then send that information back to the client by passing our dictionary 'ack' to the callback function, which becomes the resulting value of the calling Emit to Socket behaviour.

Afterwards, a timeout call for 5 seconds is made for finally beginning the round, now that everyone has all the info they need to play the game. The 'beginRound' function (line 99) controls some game-specific logic for this project. We won't go too much in detail about it, but essentially it handles where to spawn the players, checking scores, as well as telling the clients who is It.

As mentioned before, the 'init' message is called on our "Game Logic" label when everything is ready to go. In the player object, we will now look into the behaviours on it where it receives the 'init' message.

mceclip22.png

Here you can see we have several behaviour trees on our player. First off, we will start with the upper left tree.

mceclip23.png

This is the behaviour that practically starts up everything else in our game.

First, we receive the 'init' message sent from the "Game Logic" label. From there, we grab the server ID of our object and turn on one of our Socket Events as well as setting up our game screen so we can accurately follow our character.

 

Movement Syncing

mceclip24.png

Here is one of our most important behaviours. This small tree is designed to update our player's position on the server every time we move the joystick. You have probably noticed it is also referencing some dictionary values. We get those values from a standalone dictionary behaviour that contains the X and Y positions of our player.

Let's take a look at the 'moved' event on the server.

mceclip25.png

data = JSON.parse(data);

Dictionaries, when sent from a client to a server, have to be parsed in order to read the data easily. This is because dictionaries in hyperPad are encoded into a JSON structure when emitted to a server. This line parses the JSON string structure and stores the dictionary list back into the same local 'data' variable.

 

socket.x = data.x;
socket.y = data.y;

Here, we update the X and Y positions stored on the socket with the new values given by the client.

 

for (const client of room.sockets) {
if (client == socket) {
continue;
}
client.emit(socket.id, {
x: socket.x,
y: socket.y,
score: socket.score,
isIt: socket.isIt
});
}

Next, we iterate through all the clients to update them of our new position and other details, excluding ourself (i.e. The emitting client doesn't need to know their own position).

 

mceclip26.png

This tree here controls the bulk of our game. We use a Socket Event when the server is checking which player is tagged as It, which is emitted by 'beginRound' (line 99) on the server.

Then, we grab our object's server ID from the array and use the Dictionary Value to break it down into the various pieces of data it contains. From there, we grab our Score, if we are tagged as It, as well as the x and y positions of our object then apply them to the object's attributes. The rest of the behaviours is for setting and controlling the UI in our game.

 

Conclusion

 

There was a lot to take in here, but hopefully if you made it this far you should have an understanding of how to utilize the Socket.io behaviours to create multiplayer experiences for your players.

Try it yourself! Take an existing game you have created and try giving it some online functionality, such as a highscore leaderboard scene that connects to a server and requests a list of the top 10 scores with player names to display.

It's difficult to teach a scripting language such as Javascript in a single article. Thankfully, if you are having issues there are loads of other resources to help you with writing Javascript applications for Node.JS and Socket.io;

Learn Javascript - https://developer.mozilla.org/bm/docs/Web/JavaScript

Learn Socket.io - https://socket.io/docs/

Learn Node.JS - https://nodejs.org/en/docs/

 

 

Have more questions? Submit a request

0 Comments

Article is closed for comments.