With the ever growing opportunities for creating online multiplayer games for different platforms, and many reachable and easy to use open-source tools, it has become much more convenient for developers to create exciting multiplayer games. In this talk, Sahar will create a simple multiplayer game to demonstrate it and talk about the issues she faced when creating her own games.
AI Generated Video Summary
1. Introduction to Multiplayer Web Games
Hi everyone. I'm Sahar Poundasseh, an indie game dev from Iran. Today, I'll show you how to create a multiplayer web game using Phaser and Socket.io. Let's build a tic-tac-toe game together. I have prepared some code and logic in advance. On my Node.js server, I have installed Express and Socket.io and implemented an index.html file. I generate a token for the room using a random four-digit number. In the index.html file, I import the phaser platform and use socket.io. The code includes a phaser config file with four main functions.
Hi everyone. My name is Sahar Poundasseh, as you met me before. I'm an indie game dev based in Iran, Tehran and I work in a game studio called GearBag. I'm here to talk about multiplayer web games. I'm not that much of a speaker, so I wanted to show you in action how you can create a multiplayer web game from scratch and how it's really easy, so you won't get really mixed up in the complexities of it.
I'm going to be using Phaser as my engine and Socket.io Node.js as my server, but you can use anything. The basic thing is that we're going to use WebSocket and that's it. I hope you enjoy. So let's build our game.
We're going to build a tic-tac-toe game, a simple one, but because I don't have much time and I'm not that much of a fast typer, I have prepared some code, some logic in advance, the game and stuff like that. So we're going to fill in the blank of our network code together. So here on my Node.js server, I have only installed Express and Socket.io and I'm implementing and importing an index.html file. And I'm serving it here. And I only have a function that generates a token for the room.
So it's a random number, four-digit number. I'm going to use it for four digits. And why I'm using this is because I like room codes and it makes me feel special like a VIP. So I like implementing that into my games. And in my index.html file, I have these two files. I have imported the phaser platform and I also have implemented socket.io. It is built on top of WebSocket so it's basically WebSocket but I'm using socket.io right now. So in the code that is right here, I'm going to explain it really fast. It's my phaser config file. So it's just the width and height and has four main functions, create init, preload and update for the scene. I only have one scene. I'm using init function and create. So in my init I have some variables like the player character and the play log, which is going to lock the player if it's not their turn. And they're empty. And the table info, which I'm going to keep my characters in just to keep track of them.
2. Creating the Table and Game UI
I'm going to keep my characters in the table info. The table index, table metrics, holds ones and minus ones for player inputs. The table has a container, character, and zone for clicking. I'm checking if it's the player's turn and if the character of that zone is empty. There are two boxes for game UI, a welcome box, and a box for create/join room buttons. I'll connect our client to the socket server.
And the table info, which I'm going to keep my characters in just to keep track of them. And the table index, table metrics, that holds ones and minus ones. So if the player inputs O, it will be one. And if the player inputs X, it will be minus one. I'm just using this to check the ring. And some table width and cell width and start table positions.
And in my on creates I have created the table. So it's a container and phaser. And it has a character, which is empty right now. And it will hold X and O's and a zone for clicking. And I'm holding this character in the table info right here. With a place ID. So instead of using I and J separately, I'm using place from zero to eight to manage my places when doing moves inside the game. And this is an on click for the zone. So in this onclick, before emitting the move to the server and the other players, I'm checking if it's the player's turn and if the character of that zone is empty. So if there's already something there, I'm not emitting it.
And this is the table. And I have two boxes. It's just game UI. It's a welcome box. Which will write welcome and show the room token to the creator. And there's a box for holding two buttons to create room and join room. And there are two functions for unclick listener. So when we click on create a room, we should emit to our server that you should create a room for us. And for joining, I'm going to take the token from prompt from user and I'm going to emit this to the server afterwards to join the room. And some simple text right here to show a start and if it's your turn or not or you're playing with X or O. And this is it, very simple. So let's start with our client side. The first thing that we need to do, we need to connect our client to the socket server. So I'll create up my scene.
3. Creating a Room and Assigning Players
When the player opens the game, they connect to the socket. The first step is to create a room by emitting 'create room' to the server. The server generates a four-digit token and joins the user to the room. The user is notified that the room has been created with the token. The server keeps a list of players and their rooms, assigning characters to them.
So this will be called when the player opens up the game. I'm going to open up my socket and connect to it. So this side side, the socket is open. So I'm going to just connect to it. So the player will be connected to the socket like this. And I'm holding reference to it but this is that socket.
Now the first thing that we're going to do is create a room. So the user will request please socket, please create a room for me. So it will emit create room. Now if we're going to do this step by step, we're going back to our server called and listen for create room because the user is requesting that. So right here on connection, so io.onConnection, we will get a socket object referring to the user that has been connected to our socket server. We will use this connection a lot. So on this connection, we will listen, so socket.on create room. The user will not pass anything to us. They're requesting for a room token and a room to be created. So we're going to do just that. So const token equals generate token, the method and it's going to be a length of four. And now what we're going to do is that we're going to join this user to this room. So socket.join in this room. This socket holds a reference to the user and then they're going to join inside this room with this token. And next we're going to talk back to the user that your room has been created and created and this is the token. So send it to your friends so they'll be able to play with you. So to do that we're going to emit to the user that your room has been created. And this is your room token. So their room has been created and they have been joined inside their own.
But before getting back to our client side I'm going to do one more thing. So I'm going to keep a list of my players just to keep an eye on them. It's an empty area right there in my server and I'm going to push into this the id of this player and their room. So their id would be second.id and their room will be this token. I'm going to use it later and I'm going to assign a character to them right here.
4. Assigning Characters and Sharing Room Token
I'm going to assign the character x to them because they joined first. The room has been created and it will emit back a token. We'll hide the choose box and show the welcome box with the room token for sharing.
I'm going to assign the character x to them because they joined first but you can randomize it and keep track of it later. And the room has been created so it will emit back, talk back to us which room created and a token. So let's listen to that right here. So in our client side we're going to listen to that. So on room created it will give us a token. And what we're going to do with this token is that I have the code. It's just UI stuff so I'm not going to write that. I'm going to hide the choose box and show the welcome box and say that your room token is this thing that we got right here and they can share it with their friends.
5. Joining the Room and Checking for Availability
The next step is for the other user to emit a token to join the room. We listen for the 'joinRoom' event on the server and check if the room exists. We also check the number of players in the room.
The next thing that we're going to do is the other user wants to join the room so they will have to emit, so this.socket.emit.joinRoom and they will send a token to us right? The token that we got from the prompt. So back to our server code we're going to listen for joining.socket.on.joinRoom and we will get a token right? And right here we're basically doing the same thing as creating the room but first we have to check if the room actually exists. So I'm going to check with this io.socket.rooms.get so find this room see if it exists and also what I'm going to do next is I'm going to check if after this room exists how many players are actually in there.
6. Joining the Room and Starting the Game
I'm going to check if there are already two players in the room. If so, the room is not available. Then, I'll join the user to the room and add them to the players list. The room is full, so I'll notify both players that the game is starting. I'll emit the 'start game' event to the opponents of the player who requested to join, passing information about the room, characters, and turn. To the player who joined, I'll notify them that the game is starting and provide the room information. If the room is not available, I'll emit an error. On the client side, I'll listen for the 'start game' event and assign the room token, player character, and play lock based on the received information.
So I'm going to check its size and see if it's less than two. So if there are already two players in there, this room is not available anymore. And then I'm going to do basically the same thing. So I'm going to join this user to this room and then I'm going to add them to my players list. So the socket ID is the same, the token is the same, but their character will be... oh if you're randomizing that you have to check the players for the opponents and then set the character for them.
And then, what I'm going to do is that the room has been created, there are already two players, so the room is full, now the game must start. So I have to admit to both players that the game is starting and this is going in formation. So I'm going to do each of the admits separately for the two players. It's only two players so it's really easy. So with this token it will emit to this room except for the sender. It will emit to the opponents of the player that has requested to join. And I'm going to admit to them that the game is starting so start game and then I'm going to pass some information to them. Like your room is this token and you're playing as X and it is your turn to play. So turn is true. If you randomize the characters you have to read it from there but I'm just putting this right here because it's easier. And then to the player that has requested to join I'm just going to say a start game your room is this and you're playing with O and it is not your turn to play so it will become false. Now we have joined them and emitted start game. Before getting back there we have to have else. We're going to check that later. And so if the room is not available I'm going to emit back that there's an error. Okay, now let's get back to our client. Now here we have to listen to the room's start game. So this .socket.on() startGame() it will give us some info. Let me see if I put the info right. Yep. And right here what we're going to do with this info. So we're going to assign the room token right here because we're going to use it later. So info.room and the player character, equals info.player. And then we're going to assign the lock so the play lock will be info.turn.
7. Making Moves and Determining the Next Turn
If it's your turn to play, the lock would be false. Show your piece and the start text. After that, show the table. The players have joined the room and received a start game message. To make a move, emit the move to the server with the room and place information. Listen for the move on the server. Find the player's character who made the move to determine the next turn.
So if it's your turn to play the lock would be false. And then we're just going to do some pretty stuff that I have right here. All right. And it's going to show that your piece is this. And if it's your turn to play or not. And I'm going to hide the boxes and show the start text. And after everything is done, I'm going to show the table itself.
The next thing. So the players have joined, created and joined the room. And they have received a start game message from the server. So now they need to make a move if it's their turn. So how are we going to do that? I'm going to come back here to the zone that I created to emit the move. So if it's their turn to play and the character is empty, we're going to do the same thing. So this is the socket that emits. And new move has been made. We're going to pass some information. Like the place, first of all the room. The room is this.roomToken. And also the place is the place that we set before. So the number I times three plus J.
And now this will emit and move to the server. So we have to come back here and listen for that. So I get that on. Move, we're getting some information about the move, right? And the first thing that I'm going to do is that I'm going to find the player's character that has just made the move, to determine whose turn will be next. So I'm going to handle that on my server because it's safer that way. To do that, I'm going to find the player with this ID. So index equals players.map with a function that gets a player and returns that ID. And then I'm going to find index up, second, that ID. So I'm going to find the player with their second ID, their connection ID. And then if we just found the player, so it's bigger than minus one, I'm going to get the player.
8. Determining the Next Turn
So currentPlayer is going to be players of index and then I'm going to emit the move to, no, I'm going to find the turn. We have the new turn, now we're going to tell the players inside the room that this is your turn to play. We're going to say IO.to info.room.emit move. And I'm going to pass the authorized information to this. Let's see if I... Alright, everything looks good.
So currentPlayer is going to be players of index and then I'm going to emit the move to, no, I'm going to find the turn. So const new turn will be, if the current player that character is x, so what will it be the next turn? We guessed it, it will be o. And else it will be x. So we have the new turn, now we're going to tell the players inside the room that this is your turn to play. No, this is the move and let's change the terms. Sorry. So we're going to say IO.to, this will emit to all the players inside this room. So I'm not using socket.emit. So IO.to info.room.emit move. And I'm going to pass the authorized information to this. So what is the information? I'm going to say the character is currentplayer.character. And also the place is info.place. And the next turn, so the turn will be the new turn that we just indicated. Let's see if I... Alright, everything looks good.
9. Listening for Rules and Handling Moves
Now, let's get back to our client. We have to listen for the rules and handle moves. After each move, we check for a win. Finally, we handle player disconnections by kicking them out of the room and removing them from the players array.
Now, let's get back to our client. Now, we have to listen for the rules. You see, I didn't do anything for that inside the client. So, let's listen. Right here, this.socket.on and remove. We are receiving some information. And what I'm going to do with that information, I have a handle move method that is just a UI stuff. It sets the content of the text right in the cells as the move character, and it updates the table matrix with ones and minus ones. And it determines the turn.
So, if it's your turn to play the play log will be false and it will say it's your turn to play, and after each move I'm checking for the win right here to stop the game. So, on move I'm going to call my function handle move and it will need a context because of my context, yeah. So, cells are going to be this and I'm going to say this and the information I got from the move, from the server and it will handle it right here.
Now, basically everything is done, but I'm going to do one last thing to tie up all the knots because you always have to listen for that or you'll be screwed Maybe that's bad to say. Yeah. I'm not a native English speaker, so pardon my French. So, I'm stuck at the on disconnect. We're going to check if the player has disconnected from network. So, we're going to first find the players. I have the code right here. After I found the player, I'm going to kick them out of the room. So, we're going to say, players index.room. So, kick them out of this room. And also, I'm going to remove them from the players array, because if the players array gets too large, then it will consume a lot of memory and that's really not what you want. So, players.splice, from this index and one by one. All right, everything is done. And I really hope I don't have any bugs or anything. So, let's check. And let's run the server. So, node server.js. It is running.
10. Game Conclusion and Tips
I have two clients, one creating a room with token 7983, and the other joining. I play the game and win and lose at the same time. WebSocket is reliable for end-to-end data transmission, but UDP is faster and better for PVP games. Consider the best protocol for your game. Besides the client-server method, you can use a peer-to-peer network for faster data transmission and simpler coding.
So, let me just copy this. And I have my tabs ready because I'm a really prepared person. So, I have two tabs, two clients. And one of them is going to create a room. So, the token is 7983. And I'm joining from the other client.
All right. It is my turn to play. The left side is to play. I'm going to put an X there and an O here. And then I'm going to X there, O here. Right. And I win and I lose at the same time. So, the game is done. And it was really easy. I know the game is easy, but the concept is actually easy. Think about it.
But before we finish, let's get back to our slides. I have one more thing to say. And I'll be done after that. So, there are some tips. First of all, WebSocket is built on top of TCP protocol, which is a 3-way communication, and it's really reliable, and ensures end-to-end data transmission and its safety. But there's also UDP protocol, which is actually more common and more suitable for PVP games. And it's faster. And if there are some data packets that get lost on the way, it doesn't care about them. So maybe you need to use that. So think about it. What is the best thing, the best protocol to use for a game? And also, I used client-server method, here in the Tic Tac Toe game, but it's not the only method. You can use peer-to-peer network method, which the nodes are connected to each other directly, and there is no server node, so it's faster data transmission between them. And it's much simpler to code.
11. Reliability and Network Considerations
Websockets are reliable for end-to-end data transmission, but UDP is faster and better for PVP games. Consider the best protocol for your game. Besides the client-server method, you can use a peer-to-peer network for faster data transmission and simpler coding.
But it's really unreliable, because, not really unreliable, but it's somehow unreliable because there is no server authorization for the messages that are being sent. So the clients can send anything they want. And the client-server, on the other hand, first of all, is very modular, so better practice, as we know. And also, it's a little bit, it takes a little bit more time than p2p, because at the basic scenario of tic-tac-toe game, there are only two players, so one message is sent twice the time. So one sends it to server, and server sends it to player two. So it takes a little bit more time. And also, yeah, I guess this is it. Maybe there were some more things, I can't remember.
12. Network Code Considerations and Code Availability
Start early on your network code and think about it early in your code. Be aware of the amount of information you're transmitting across your peers. Thank you for listening. Websockets are the top option for multiplayer games. It's helpful to have a non-option to understand the usage of real-time networking APIs. The code is available on GitHub.
The next thing is that start early on your network code. So don't say that, I'm going to finish my game to the fullest, and then I'm going to add my network logic to make it multiplayer. It's going to be much more difficult and much more complex. So think about it early in your code and do it simultaneously. And also, be aware of the amount of information you're transmitting across your peers, because if the amount of information gets really large, it will slow down your server and not slow down, but the message will come slower and it will not create a good experience for the players.
Now, I hope you enjoyed this talk and thank you for listening. I'll see you the rest of the evening. Goodbye for now.
Do you want to look at these poll results? It looks like... Yeah, sure. I'd love to do that. Websockets are the top option here. That's actually good because if you already know Websockets, you're more than halfway there. Start developing multiplayer games. I think it was helpful to have a non-option here because that was something... We had a similar poll yesterday. We couldn't tell. I think there was another option and we couldn't tell if people were using other technologies or if they weren't making multiplayer games. I feel like this helps. I wanted to know if people have not been working with any of these real-time networking APIs or maybe they have. That's cool to know. You're halfway there. Yeah. Cool.
I think we can take a look at some of the questions you've been sending in. The first one is also a question I have. Is the code that you were showing in the available somewhere like GitHub or something if I wanted to try it out and play around with it? Yeah, absolutely. I'll post the link to GitHub on the channel. Cool. Maybe you can tweet it out too.
I will, of course. Who may not be on Discord to see it too. Awesome. Cool.
And then another question. I don't know too much. I'll just ask it. Can we, is it possible to use UDP in web games today? Is that something we can do?
Actually, no, because there are some network issues, maybe network problems like VDOS attacks that make it not as safe to use UDP protocol in web, but the closest thing we have to that is using WebRTC, which is actually not my preference because it's really... first of all, it's a P2P on its own and it's harder to implement. So I think using TCP with WebSocket is the way to go.
That makes sense. But would that mean that WebRTC would be better in terms of... It wouldn't necessarily have server costs. So if you're making like a small game or something because it's P2P, you wouldn't...
Yeah, a small games, like a small room like Tic-Tac-Toe actually could be made with WebRTC, but as the rooms get bigger, especially, there are more problems like for P2P and WebRTC. Like on mobile games, each client has to send their messages to like a hundred people and they would use much data like connection and it wouldn't be very good practice.
That makes sense. But it sounds like maybe if it's like a one-on-one game like a chess or something where you only have two players, then maybe WebRTC would be a good option. That would be good. Cool.
Oh, I made a follow-up question for that. But even though it's P2P, you still need some server to make the initial connection between the peers?
Exactly. You can use ICE on your server to do that. Like I said, it's harder to implement. So maybe for a smaller game, it's better to use actually the web sockets, because it's easier to implement. It makes sense. Cool.