By interacting with the Colyseus community over the past few years, some fundamental questions (not necessarily connected with the framework) seem to appear time and time again when developers start to build their own multiplayer games. This talk is going to cover some of these questions, as well as the most common scenarios and techniques you can start using today when building your own multiplayer game.
Making Multiplayer Games with Colyseus, Node.js and TypeScript
AI Generated Video Summary
Today's Talk covers making multiplayer games with Coliseus, Node.js, and Typescript. It explores the state of networking on the web, alternative servers, and how Coliseus works. The Talk also discusses client-side prediction, lag compensation, server limitations, scaling, and showcases cool games made with Coliseus. Additionally, it mentions Nakama and Unity integration for client-side prediction in multiplayer games. Coliseus is available on mobile and can be found on colossus.io or the GitHub repository.
1. Introduction to Coliseus and Networking
Today, I'm going to talk about making multiplayer games with Coliseus, Node.js, and Typescript. We'll discuss the state of networking on the web, alternative servers, how Coliseus works, client-side techniques, and multiplayer games built with Coliseus. The state of networking on the web currently relies on WebSockets and WebRTC, with the potential future inclusion of Web Transport. While TCP is the only option for web-based games, UDP is generally preferred. However, many successful games have been made using TCP and WebSockets.
So, the topics of this talk are the state of networking on the web, alternative servers, how Coliseus works and its internal systems, some client-side techniques that you can apply even outside the scope of Coliseus, and a few multiplayer games built with Coliseus.
So, the state of networking on the web. Right now we have, for bidirectional connections, we have mostly WebSockets and WebRTC available. Web Transport, hopefully, is going to come in the future. WebSockets is TCP only and is available since 2011. WebRTC has been fully standardized only in the last year and is fairly complicated and involves a lot of other protocols and it does support reliable and unreliable connections. WebTransport, hopefully, is going to replace WebSocket in the future. Nobody knows when. And it's very exciting. It does support reliable and unreliable delivery and has experimental support already on Chrome since January this year. General advice for networking outside of the web is that TCP is not ideal for games and UDP is, it's hard to disagree of this, but unfortunately the web only has TCP as of now. Hopefully web transport is going to change that in the future and many successful games have been made in the past using TCP and WebSockets is what we got and there's a lot of successful games made on top of WebSockets.
2. Alternative Servers and Coliseus Framework
In an alternative approach, the server validates and determines the state of the game, rather than relying on the client. Coliseus is a Node.js framework that uses WebSockets for transport. It provides matchmaking, room synchronization, and messaging. Rooms in Coliseus have lifecycle methods and can be created when clients request to join or create them. The matchmaking flow involves an HTTP request for seat reservation, querying for existing rooms, creating a room if necessary, and establishing a WebSocket connection. After the connection is established, the client receives the full room state and can start exchanging messages.
Okay, without further ado let's talk about alternative servers. So in an alternative approach you wouldn't trust the client so the client can say for example where he is or the client should never dictate information. The server should always be able to validate and tell the truth about the state of the game. So this is not very alternative, it might be fine if you're okay with this so this wouldn't be feasible on a multiplayer competitive game.
So an alternative to this is give more, give less information for the server. Let's say I'm pointing at a certain angle and moving forward so the server has the current position and he's going to the server is going to determine what's the next one and not the client. So this is alternative the responsibilities of the server is to hold the game logic, game state, validate client inputs and exchange messages with the clients and also the state. The responsibilities of the client is basically to be a visual representation of what's in the server and send inputs and actions for the server.
What Coliseus brings to the table, it is a Node.js framework. It's built only with WebSockets, so there's only WebSocket as a transport layer so far on Coliseus. It matchmakes players into rooms and has a built in room synchronization and message system. And it has the build blocks and the architecture so you can scale this to many servers and have many servers handle multiple rooms. And as you can see, rooms is a very basic block from Coliseus, and this is how a room definition looks like, and it has its life cycle methods, such as onCreate to set up a match, onJoin when some player joins the room, onLeave to clear this player from the room state and the other clients can react to this change, and onDispose when this room has been destroyed on the server. If you have a shared global state or something on the database, this is a good place to clear global things that this room has possibly created. In order for the clients to join this room, you need to expose this to the matchmaker. You see that there is no actual room being created at this point. Rooms are only created when the client requests to join or create them. On this example, it's the client requesting to join a game room and providing some information about himself, such as the name. For the matchmaking request, this is how the flow looks like. The client makes an HTTP request to ask for a seat reservation. The server is going to query for possible rooms that already exist. If it doesn't exist, it tries to create one and returns the room ID and session ID, which is the session reservation. After he got the session reservation, it tries to actually connect through web sockets. This is how the flow looks like from the server side perspective. First it tries to validate the user during on-off. This is totally custom and based on your own requirements. If that succeeds, it tries to call on join. At any point, you could throw an error here and the client would hit the catch block here. Yes, you could throw an error from the server and here it would go in the client. No error has happened during this process, the connection is established, and after the connection is established, the first thing is the client receives the full room state, so the client can already build the visual representation that that room has on the server and start exchanging messages and more state patches and more messages, and then it's regular web socket and bidirectional stuff.
3. Message Listening and State Mutation
The onMessage API is fairly simple, allowing you to listen and send messages between client and server. The server can send messages to specific clients or broadcast messages to all clients in a room. The room state is based on mutable structures, synchronized every 15 milliseconds, and mutations should only be performed on the server. The structures used for the state are called Coliseus schema, which provide strong typing, incremental serialization, low output, and a client-side callback API. In this example, a board game is used to illustrate the creation of a state and assigning players based on session ID.
The onMessage API is fairly simple, so this is how you listen and send messages between client and server. The server can send a message to a particular client based on the client reference, and the server can also broadcast messages to every client inside this room, so there is no built-in way for that the server sends the broadcast for every client on every room. If you need to do that, you need to implement that for yourself.
The room state is based on mutable structures, so you can't really use any immutable structure as you would in front-end frameworks, and the mutations that you perform on those structures are synchronized automatically, at every 15 milliseconds by default. And those mutations are on-way, so you should only mutate state data on the server, so they are broadcasted back to the clients. You should never mutate the state in the client itself.
Right, the structures we use for the state are called Coliseus schema. It is strongly typed, it supports incremental serialization, has the lowest output we could possibly implement, and it provides a client-side callback API. I'll explain a bit about this in a few. It's very inspired by other serialization libraries such as protocol buffers, flat buffers, and others. On this example, imagine a board game. You have a map of players here, and the player has a position. A position on the map, or on the board. And on the room code, you would initially create a state, so you set state during creation, and whenever a player joins this room, we assign to the map of players a new player based on the session ID of that client.
4. Client-side Prediction and Lag Compensation
The session ID is the unique identifier for this client on this room. On the client side, every client should listen to adds and removals from this player structure. What's the difference between messages and room state? Messages are ephemeral, while state is persistent. On multiplayer games, lag is inevitable, and developers should try to alleviate the perceived lag from the current player. Client-side prediction can be used to provide an immediate response to player actions. Linear interpolation is a simple technique that can have good results.
The session ID is the unique identifier for this client on this room. And whenever the client sends a move message, it gets the player reference and increments his position. As you can see this is not very alternative. Any player could send this at any moment and it would increment his position. So ideally, there must have some sort of validation for, like, is this player in his current turn? Does he have any pending valid moves left? Like, some checks like this.
Yeah, so on the client side, every client should listen to, like, adds and removals from this player structure. So these are the callbacks you would register on the client to prepare the visual representation. This is listening to the position attribute change. A cool thing of using TypeScript is that if you do provide—this is optional by the way—if you do provide the type reference for the room state here, you would have auto completion for all the properties and everything inside the state during development.
Yeah, so what's the difference between messages and room state? Well messages are ephemeral and they're not persistent anywhere. So whenever you send a message, only the current connected clients are going to receive it. State on the other hand is persistent. If you set something on the state and a new client joins, that new client will receive that data. Right, so on multiplayer games lag is inevitable, and we should as developers should embrace it and just try to alleviate the perceived lag from the current player. Yeah, so take this like you have the client at position 10-10, then he sends the action to move forward and the server here, he gets that he's at 11-10, but the client could have moved immediately here, and he's going to receive back this data from the server only a couple milliseconds later. This is called the round-trip time, and yeah, this is half a round-trip time. And you could use this value to perform some techniques on the client side, to try to at least give the player an immediate response. So this example from Half the Opposite implements client-side prediction, which he basically processes players' input immediately on the client side. So whenever you press the keys to move, your player moves immediately. On the server, it cues the player actions and processes the queue at every server tick. Yeah, you can have a look at the source code there. It is applicable if you want to use this approach outside of Colesios as well. There's this really nice presentation from GDC explaining Overwatch gameplay and netcode. So I highly recommend you check this. They explain the whole client-side prediction that they did there. And yeah, they also say some techniques for dropped packets. And we can't really apply any of this on WebSockets because there's no dropped packet on a reliable connection. When we do have on web transport, we could apply such techniques. So linear interpolation is very simple and can have good results. On Mesmora, this game I made, I used linear interpolation everywhere.
5. Server Limitations, Scaling, and Cool Games
You see that this is a tile-based game and the player moves across the tiles with linear interpolation. Having a deterministic physics is important. Server limitations, like how many CCU can a server have? I could achieve that on a cheap server, a WebSocket server running a card-slash-board game that is very slow on the pace of messaging, the server could handle 3K concurrent connections. Rooms in Coliseus are stateful and games are generally stateful. There's a persistence layer that allows any node to matchmake. So how many CCUs can Koliasis handle? It depends. I would recommend avoiding having a very large room state and optimizing your game loops to use the least amount of CPU cycles as possible. Some cool games made with Coliseus: Tiny Dolby's game, the Open Source IO Shooter, Raft Force, School Break, and Kurka.io. Night's Edge by Lightfox Games.
You see that this is a tile-based game and the player moves across the tiles with linear interpolation. Locked time step is very important for a physics-based game. It's also called fixed ticker rate. Having a deterministic physics is important because then you could have many clients and the server if you need to, simulating the physics with different FPS and having the same output.
So, server limitations, like how many…I hate this question, how many CCU can a server have? I mean you can find some material on the internet that people managed to have 1 million WebSocket connections on a single server, but that's not very realistic. You could achieve that mostly with idle connections, not exchanging any messages, and that's not realistic at all. What I could achieve is that on a cheap server, a WebSocket server running a card-slash-board game that is very slow on the pace of messaging, the server could handle 3K concurrent connections. But I wouldn't recommend having a single room with those many connections. 50 to 100 would be the ideal, and if you need more connections than that, you can have more rooms across other servers to handle this load. So, yeah, then scaling comes into play. And scalability, the rooms live in memory in Coliseus, so rooms are stateful and games are generally stateful, so you really need to, you can't use stateless approaches to, for games I don't understand how people always recommend being stateless when scaling things. And there's a persistence layer that allows any node to matchmake. So to recap, this is how the seed reservation works on a single server and when you have multiple servers, the seed reservation request would come to the load balancer and it would be forwarded to any of the active servers. And then any of the active servers would return the reservation. And then, having this information, we can make the WebSocket request directly to the server that that room is living. So how many CCUs can Koliasis handle? It depends. Yeah, I don't know. It depends on your game. You have CPU and memory limits, and everybody does, no matter if you're using Koliasis or not. So for Koliasis itself, I would recommend avoiding having a very large room state, which would increase the throughput of your rooms, and optimize your game loops to use the least amount of CPU cycles as possible. So yeah, I'm running out of time here already. The additional tooling we have is this monitor for development, it can be really useful. A load test for creating small bots and tests, how far your servers can go with automated load testing.
Some cool games made with Coliseus. This was the very first well-made game with Coliseus by Tiny Dolby, it's still available here, I believe this was released in 2015. This is made from the community, it's called the Open Source IO Shooter, it can be found here on this link. Raft Force, me and Tiny Dobbins made with Default Engine, it's also a web-game available here. School Break made by Tobias, it's also available to play here. And Kurka.io is the first player shooter I've seen that is using Coliseus and it's very fun. And finally, Night's Edge by Lightfox Games.
6. Unity Game and Poll Results
This is a Unity game, also available to download. I hope you learned something. Let's take a look at the poll results. WebSockets seem to be the top option for multiplayer games. Socket.io should have been on the list. Many people are using Coliseus. What is Nakama?
This is a Unity game, also available to download. And that's it. I hope you learned something. I'm here, waiting for your questions. Thank you so much.
So let's first take a look at the poll results for the question we asked at the beginning. Oh! None of them. We have. I was actually interested about that. If that means people haven't made multiplayer games, or if they have and they just didn't use one of these options. Yeah. Because I don't know what else you would be using. Yeah. I was expecting playing WebSockets to be the leader.
Well, that's how I interpret this. Like for people who made, I think, multiplayer games, it sounds like WebSockets are the top option. Which makes sense because, at least for me, I've only made stuff in Socket.io, which I interpret as, it's basically just a layer of framework around WebSockets. Yeah. Oh, yeah. Socket.io should have been on this list as well. Yeah, probably. That's why I was wondering if it wasn't. No, that's cool. And then there's a bunch of people using, sorry, can you pronounce it, Kolesius? Kolesius? Is that how you pronounce it? Yeah, Kolesius, yes, correct. Kolesius, yes. Thank you. Cool. There's a bunch of people using that too. And what is Nakama? I haven't heard of that one.
Nakama and Web Game Development
And what is Nakama? They're already in Go, so not many people have heard of it. The process of making HTML5 browser games can differ from placing elements on a screen via HTML, CSS, etc. You can have a regular game without using Canvas, but it's more common to use Canvas2D or WebGL. There are plenty of options for tools and frameworks, including little.js, Pixie.js, Play Canvas, and Babylon.
And what is Nakama? I haven't heard of that one. Which one? Sorry? Nakama. Nakama, yeah, they're already in Go, so I think that's why not many people have heard of it. They are kind of similar, but they're already in Go. Cool, good to know.
Cool, I think we can jump to some of the audience questions now. So I think the first one we have is a more general question about web games. The question says, how different is the process between placing elements on a screen via HTML, CSS, et cetera, versus making one of these HTML5 browser games? And there's a second part to it. How would you describe the current ecosystem of tools and frameworks for building these types of browser games? Especially those old retro slash game-like games? Yeah, it depends a lot. I'm not sure how to answer this question, but you can have a regular game without using Canvas at all if you manage to. It can be more tricky, but it's possible. Usually you would use Canvas2D or WebGL to actually render things in real-time. What is the second portion of the question? It's just how would you describe the current ecosystems tools and frameworks. Maybe another way to phrase it, what frameworks do you use or would you recommend? There are plenty of options currently, actually. We had a talk recently about little.js. It's one tool that you could use. I think from what I saw on that talk, it's mostly for very small outputs, so you would use that. That could possibly be used for those ad games that you see. I'm not really sure. I'm just assuming it's small. But yeah, I personally like using Pixie.js. I think tomorrow we're going to have a talk from Play Canvas. Play Canvas is also really great for 3D and we had a talk about Babylon. There's so many options. It's hard to count, actually. Yeah. I also second the Play Canvas because I like it. Also, if you're used to having an editor and things where you can place things, I think that helps a lot. Play Canvas does that.
Client-side Prediction and Unity Integration
Client-side prediction in multiplayer games can be complicated. It involves executing actions on the client side before receiving confirmation from the server. This approach allows for immediate response to player actions but can lead to challenges with player-to-player collision. Games like Brawl Stars and League of Legends do not have player-to-player collision due to its complexity. Unity users can use Coliseus with a dedicated client and there are games made with Unity that utilize Coliseus.
Play Canvas does that. Great. We have another question just generally about client-side prediction and how it works. Can you talk just a little bit about that?
Yeah, that can be complicated. Very recently, I wrote a client-side just for simple movements. For example, it depends also on how is your input. For example, if you were using your keyboard as an input, you usually would send a message at every single frame, which can sound a bit ridiculous, but that's how it works. I mean, how is one approach to this? And then it's, I can't really explain properly, I miss my words, but it's basically you trying to execute on the client side before the server really sends the message where the thing actually is on the server. So maybe one way to think about it, because I think you mentioned this a little bit in your talk where you have a player and the angle of the player and the player wants to move forward. And instead of waiting for the server to say, you're going to be in this position, you can assume that you're going to move forward. And then when you get it back, you can say that. Yeah. It makes sense. And what can be a bit tricky about this is that you, let's say you move your client forward, but you have another... That's why it's really complicated to have a player to player collision on multiplayer games. For example, you see so many multiplayer games that don't have player to player collision because it's tricky. It's complicated. Like, Brawl Stars, like most point and click like Dota. I think League of Legends also don't have it, I'm not sure. But you have your local simulation, and like, which client should this server trust? It's easy to get a misaligned. Yeah. Cool. No, that makes sense. That sounds much more complicated. That obviously comes with a trade-off of it'll be, it'll look better if you can pull that off, because then you'll have seemingly less lag.
I think we have one more question. Someone's asking about Unity. If they're using Unity, is it possible to use Colossus? Is there a plugin or a way to use it with like a Unity WebGL plug support?
Yeah, it is. There is a client available for Unity. And yeah, there's one game that was made with Unity.
Coliseus Availability and Conclusion
Coliseus is available on mobile and can be found on colossus.io or the GitHub repository, which provides all the necessary links and information.
It's available on mobile. It's really cool. Awesome. And if they wanted to find that, would it be on like the GitHub or the web page, the Unity? Yeah, there's colossus.io. And from the GitHub repository, you can find pretty much all the links, all the information.
Awesome. Well, I think that's all the questions we have. Thank you so much, Endo. It was great having you here. Cool.