Colyseus — Minimalist MMO Game Server for Node.js
For the last couple of months I’ve been prototyping some multiplayer games in HTML5/JavaScript. I thought it wouldn’t be painful since the community has grown a lot — having a library ready-to-go for everything you want out there. Unfortunately it doesn’t seem to be the case for multiplayer games.
Most people who want a multiplayer game tend to re-implement a lobby system using socket.io or sockjs, which is fine if you want to do a single game. What I wanted was just a lobby system with rooms support, and synching room state without the hassle of managing particular messages back and forth.
So the idea of having a generic game server evolved and Colyseus does just like that. It basically is a match-making system which spawns rooms with custom code and sync its state with all connected clients.
Here’s an overview of how it works:
Match-making cycle:
1. User asks to connect in a specific room, with optional “join” arguments.
// (client-side)
var Colyseus = new Colyseus("ws://localhost:3553");
var battleRoom = colyseus.join('battle', {options: 'here'})
2. Server will loop through all spawned room handlers named ‘battle’ and call requestJoin method against that handler.
// (server-side)
// only accept new users if limit isn't reached
requestJoin (options) {
return ( this.clients.length < 12 )
}
3. If requestJoin succeeds, a method named onJoin will be called with the client reference and client options given.
// (server-side)
onJoin (client) {
// setup new player here
}
4. If requestJoin fails on every available room, a new handler will be spawned if requestJoin succeeds in a fresh handler state. In case it fails again, an ‘error’ event will be dispatched on client room instance.
// (client-side)
battleRoom.on('error', function(message) {
console.log("error triggered on battle room:", message)
})
Room state handling:
Once the player received the room state upon its connection, he only needs to receive the state difference on later broadcasts, called patches.
By default, room handlers will deliver the patched room state to all clients each 50ms.
// (client-side)
battleRoom.on('update', function(newState, patches) {
console.log(patches) // array of patches
console.log(newState) // newState with patches applied
})
The array of patches usually look like this: (JSON-Patch format)
[
{ "op": "remove", "path": "/players/N150OHMve" },
{ "op": "add", "path": "/players/NkfeqSGPx", "value": {"x": 10, "y": 10} },
{ "op": "replace", "path": "/players/NkfeqSGPx/x", "value": 5 }
]
// JSON-Patch Spec - RFC6902
You can take a look at patch handling on tanx project to see how it looks like.
Here’s some game prototypes you can play online:
- PlayCanvas tanx fork (original game is here)
- DoTower (made in 72h for LD35)
- Redneck River Race (made in 72h for LD34)
Colyseus’s code base still quite straightforward, which is good for its growth as a library. It doesn’t do much, but it tries to do really well what is meant to.
Thanks for reading until here, and feel free to ask questions on gitter chat channel in case you have any.