SlideShare a Scribd company logo
1 of 92
Making
Online Multiplayer Game
Experiences
With Colyseus and Node.js
This presentation is from Jan 2020.
It was supposed to happen on Node Atlanta 2020,
but unfortunately it got cancelled.
Hi, I’m Endel 👋
👨💻 Software Engineer and Game Developer from Brazil
🎓 Game Development & Digital Entertainment
GitHub / Twitter: @endel
I DON’T LIKE SOCCER
Summary
● Introduction
● Basic concepts
○ How Online Multiplayer Games Work
○ Authoritative Game Servers
● Colyseus in-depth
○ Match-making
○ Game State & Serialization
○ Infrastructure
● Client-side Techniques
A naive beginning (2015)
● Socket.io
● Not authoritative
● Synchronization issues between clients
Server
Broadcasts messages back
to clients
Client 1.
State
Client 2.
State
Client 3.
State
100ms 100ms 150ms
A naive beginning (2015)
Not authoritative
A naive beginning (2015)
All I wanted
● Be able to handle multiple game sessions
● Manipulate data structures in the server
○ Have them automatically synchronized with the clients
Colyseus
Why Colyseus?
● Multiplayer games should be easier to make
● Should be more accessible
● Should work on as many platforms as possible
● Should be open-source!
Client-side Integration
● HTML5 (JavaScript / TypeScript)
● Unity3D (C#)
● Defold Engine (LUA)
● Haxe
● Cocos2d-X (C++)
How Online Multiplayer Games Work?
● Peer-to-peer
● Client as a host
● Hybrid (of two above)
● Client / Server
Server
Dumb client Dumb client Dumb client
Responsibilities of the Server
● Hold the Game State
● Hold the Game Logic
● Validate client inputs
● Send the Game State to the clients
Server
Dumb client Dumb client Dumb client
Responsibilities of the Client
● Visual representation
● Apply game state updates in a pleasant manner
● Send inputs (actions) to the Server
Server
Dumb client Dumb client Dumb client
Authoritative Game Servers
My new position is
now [x, y]
*mxlvl.com: This game was made by @x100
Authoritative Game Servers
My new position is
now [x, y]
*mxlvl.com: This game was made by @x100
Authoritative Game Servers
I’m pointing at angle
X and moving
forward
*mxlvl.com: This game was made by @x100
Authoritative Game Servers
I’m pointing at angle
X and moving
forward
*mxlvl.com: This game was made by @x100
Colyseus
● Uses HTTP/WebSockets
● Game Rooms
● Match-making
● Room State Serialization
● Scalability*
Colyseus
Game Rooms
Colyseus
Game Rooms
The match-maker has
instantiated this room.
Rooms are in-memory, and
live on a particular Node.js
process
Colyseus
Game Rooms
Client is asking to join
into this room
Colyseus
Game Rooms
Client sent a message to
this room
Colyseus
Game Rooms
Client has been
disconnected
(or left intentionally)
Colyseus
Game Rooms
The room is being
destroyed.
(It’s a good place to persist
things on the database)
Rooms are disposed
automatically when the last
client disconnects
(unless autoDispose=false)
Colyseus
Game Rooms
Colyseus
Game Rooms
Unique identifier for
this room type
Colyseus
Game Rooms
● Rooms are created during matchmaking
● A client can be connected to multiple rooms
● Each room connection has its own WebSocket connection
● client.join( “game”, { options } )
● client.create( “game”, { options } )
● client.joinOrCreate( “game”, { options } )
● client.joinById( roomId, { options } )
● client.reconnect( roomId, sessionId )
Colyseus
Matchmaking (Client-side)
Colyseus
Matchmaking (Client-side)
try {
const room = await client.joinOrCreate("game", {
name: "Jake Badlands"
});
} catch (e) {
console.log("Could not join!");
console.error(e);
}
Colyseus
Matchmaking
ient.joinOrCreate("game", {
s"
t join!");
class MyRoom extends Room {
maxClients = 4;
async onAuth(client, options) {
// retrieve user data from auth provider
return true;
}
onJoin(client, options) {
// mutate the this.state
console.log(options.name, "joined!");
}
}
Client-side: Server-side:
http: request seat reservation
roomId / sessionId
WebSocket request
Connection established
Colyseus
Matchmaking
Full Room’s State
... State patches
Connection established
Colyseus
Room State & Serialization
... State patches
● The Room State is MUTABLE
● Patches are broadcasted at every 50ms
○ Customizable via this.setPatchRate(ms)
( But first, a bit of a background… )
Colyseus
Room State & Serialization
👎 Serialization: back in v0.1 ~ v0.3 👎
● Deep JavaScript Object Comparison
● JSON Patch (RFC6902)
● Patch size is too large
[
{ "op": "remove", "path": "/players/N150OHMve" },
{ "op": "add", "path": "/players/NkfeqSGPx", "value": {"x": 10, "y": 10} },
{ "op": "replace", "path": "/players/NkfeqSGPx/x", "value": 5 }
]
Previous State ⇔ Current State
👎 Serialization: back in v0.4 ~ v0.9 👎
● “Fossil Delta” Binary Diff Compression
● Hard to detect a change in the state
● Creates a new copy when decoding
var currentState = this.state;
var currentStateEncoded = msgpack.encode( currentState );
// skip if state has not changed.
if ( currentStateEncoded.equals( this._previousStateEncoded ) ) {
return false;
}
var patches = delta.create(this._previousStateEncoded, currentStateEncoded);
CPU Intensive
Serialization: v0.10+
● @colyseus/schema
● Schema-based
● Strongly-typed
● Binary serialization
● Inspired by protobuf / flatbuffers / avro
Serialization: v0.10+
● @colyseus/schema
● Schema-based
● Strongly-typed
● Binary serialization
● Inspired by protobuf / flatbuffers / avro
🔲 Encode/Decode only fields that have changed
🔲 No bottleneck to detect state changes
🔲 Mutations should be cheap
🔲 Avoid decoding large objects that haven't been patched
🔲 Better developer experience on statically-typed languages (C#, C++)
Serialization: v0.10+
Checklist
✅ Encode/Decode only fields that have changed
✅ No bottleneck to detect state changes
❓ Mutations should be cheap (need more benchmarks, so far seems ✅)
✅ Avoid decoding large objects that haven't been patched
✅ Better developer experience on statically-typed languages (C#, C++)
Serialization: v0.10+
Checklist
class Player extends Schema {
@type("number") x: number;
@type("number") y: number;
}
const player = new Player();
player.x = 10;
player.y = 20;
player.encode()
[ 0, 10, 1, 20 ]
Definition Usage
Serialization: v0.10+
Demo
class Player extends Schema {
@type("number") x: number;
@type("number") y: number;
}
const player = new Player();
player.x = 10;
player.y = 20;
player.encode()
[ 0, 10, 1, 20 ]
Definition Usage
Serialization: v0.10+
Demo
class Player extends Schema {
@type("number") x: number;
@type("number") y: number;
}
const player = new Player();
player.x = 10;
player.y = 20;
player.encode()
[ 0, 10, 1, 20 ]
Definition Usage
Serialization: v0.10+
Demo
@colyseus/schema
Colyseus
Room State & Serialization
import { Schema, type, MapSchema } from "@colyseus/schema";
class Player extends Schema {
@type("number")
position: number = 0;
}
class GameState extends Schema {
@type({ map: Player })
players = new MapSchema<Player>();
}
Game State Example
class MyRoom extends Room {
onCreate(options) {
this.setState(new GameState());
}
onJoin(client, options) {
this.state.players[client.sessionId] = new Player();
}
onMessage(client, message) {
if (message.move) {
this.state.players[client.sessionId].position++;
}
Colyseus
Room State & Serialization
Room State Example
Colyseus
Room State & Serialization
const room = await client.joinOrCreate("game");
room.onStateChange((state) => {
console.log("You are:", state.players[ room.sessionId ])
});
Client-side: Receiving Patches
Colyseus
Room State & Serialization
const room = await client.joinOrCreate("game");
room.onStateChange((state) => {
console.log("You are:", state.players[ room.sessionId ])
});
Client-side: Receiving Patches
What exactly has changed?
Colyseus
Room State & Serialization
const room = await client.joinOrCreate("game");
room.state.players.onAdd = (player, sessionId) => {
console.log("player joined", sessionId);
player.listen("position", (position) => {
console.log("position changed", position);
});
}
room.state.players.onRemove = (player, sessionId) {
console.log("player left", sessionId);
}
Colyseus
Room State & Serialization
const room = await client.joinOrCreate("game");
room.state.players.onAdd = (player, sessionId) => {
console.log("player joined", sessionId);
player.listen("position", (position) => {
console.log("position changed", position);
});
}
room.state.players.onRemove = (player, sessionId) {
console.log("player left", sessionId);
}
Player joined
Player left
Colyseus
Room State & Serialization
const room = await client.joinOrCreate("game");
room.state.players.onAdd = (player, sessionId) => {
console.log("player joined", sessionId);
player.listen("position", (position) => {
console.log("position changed", position);
});
}
room.state.players.onRemove = (player, sessionId) {
console.log("player left", sessionId);
}
Particular
property
changes
Colyseus
Room State & Serialization
const room = await client.joinOrCreate<GameState>("game");
room.state.players.onAdd = (player, sessionId) => {
console.log("player joined", sessionId);
player.listen("position", (position) => {
console.log("position changed", position);
});
}
room.state.players.onRemove = (player, sessionId) {
console.log("player left", sessionId);
}
Autocompletion
💖 TypeScript 💖
Colyseus
Room State & Serialization
const room = await client.joinOrCreate("game");
room.state.players.onAdd = (player, sessionId) => {
console.log("player joined", sessionId);
player.listen("position", (position) => {
console.log("position changed", position);
});
}
room.state.players.onRemove = (player, sessionId) {
console.log("player left", sessionId);
}
(Consistency?!)
● By default, the entire state is sent to everyone
○ This way no “private” data can be stored in the state
● @filter() let you define a filter callback per property, per client
Colyseus
State Filters! (Experimental)
Colyseus
State Filters! (Experimental)
class Entity extends Schema {
@filter(function(client, value, root) {
// this = instance of Entity
// client = the client which this value is being filtered for
// value = `secret` value
// root = the Root State
return false;
})
@type("number") secret: number;
}
Colyseus
State Filters! (Experimental)
class Entity extends Schema {
@filter(function(client, value, root) {
// this = instance of Entity
// client = the client which this value is being filtered for
// value = `secret` value
// root = the Root State
return false;
})
@type("number") secret: number;
}
Colyseus
State Filters! (Experimental)
class Entity extends Schema {
@filter(function(client, value, root) {
// this = instance of Entity
// client = the client which this value is being filtered for
// value = `secret` value
// root = the Root State
return false;
})
@type("number") secret: number;
}
Colyseus
State Filters! (Experimental)
class Card extends Schema {
@filter(function(this: Card, client, value, root?: RootState) {
return root.
players[client.sessionId].
cards.indexOf(this) !== -1;
})
@type("number") number: number;
}
Card Game Example:
Only Card owners can see their own Card data
Colyseus
State Filters! (Experimental)
class Player extends Schema {
sessionId: string;
@filter(function(this: Player, client: any, value: Card) {
return this.sessionId === client.sessionId;
})
@type([Card]) cards = new ArraySchema<Card>();
}
⚠️ Array and Map filters are not currently supported ⚠️
(Planned for version 1.0)
Colyseus
State Filters! (Experimental)
class State extends Schema {
@filter(function(this: State, client: any, value: Player) {
const player = this.players[client.sessionId]
const a = value.x - player.x;
const b = value.y - player.y;
return (Math.sqrt(a * a + b * b)) <= 10;
})
@type({ map: Player }) players = new MapSchema<Player>();
}
⚠️ Array and Map filters are not currently supported ⚠️
(Planned for version 1.0)
using Colyseus.Schema;
public class State : Schema {
[Type("string")]
public string fieldString = "";
[Type("number")]
public float fieldNumber = 0;
[Type("ref", typeof(Player))]
public Player player = new Player(
[Type("array", typeof(ArraySchema<
public ArraySchema<Player> arrayOf
ArraySchema<Player>();
[Type("map", typeof(MapSchema<Play
Client-side integration (C#)
● npx schema-codegen 
State.ts 
--csharp 
--output client-state
generated: State.cs
Colyseus
Room State & Serialization
using namespace colyseus::schema;
class State : public Schema {
public:
string fieldString = "";
varint_t fieldNumber = 0;
Player *player = new Player();
ArraySchema<Player*> *arrayOfPlaye
ArraySchema<Player*>();
MapSchema<Player*> *mapOfPlayers =
State() {
this->_indexes = {{0, "fiel
this->_types = {{0, "strin
this->_childPrimitiveTypes
Client-side integration (C++)
● npx schema-codegen 
State.ts 
--cpp 
--output client-state
generated: State.hpp
Colyseus
Room State & Serialization
import io.colyseus.serializer.schema.Schema;
class State extends Schema {
@:type("string")
public var fieldString: String = ""
@:type("number")
public var fieldNumber: Dynamic = 0
@:type("ref", Player)
public var player: Player = new Pla
@:type("array", Player)
public var arrayOfPlayers: ArraySch
Client-side integration (Haxe)
● npx schema-codegen 
State.ts 
--haxe 
--output client-state
generated: State.hx
Colyseus
Room State & Serialization
Colyseus
Sending Messages
● Messages use MsgPack by default
room.onMessage((message) => {
console.log("received:", message)
})
room.send({ action: "hello" })
Client-side
onMessage(client, message) {
if (message.action === "hello") {
this.broadcast("world!");
this.send(client, "world!");
}
}
Server-side
Colyseus
Sending Messages
● Messages use MsgPack by default
room.onMessage((message) => {
console.log("received:", message)
})
room.send({ action: "hello" })
Client-side
onMessage(client, message) {
if (message.action === "hello") {
this.broadcast("world!");
this.send(client, "world!");
}
}
Server-side
Send message to everyone
Colyseus
Sending Messages
● Messages use MsgPack by default
room.onMessage((message) => {
console.log("received:", message)
})
room.send({ action: "hello" })
Client-side
onMessage(client, message) {
if (message.action === "hello") {
this.broadcast("world!");
this.send(client, "world!");
}
}
Server-side
Send message to a single
client
Colyseus
Handling reconnection
● What if one of my clients drops the connection?
○ Closed/Refreshed Browser
○ Switched from Wifi / 3G
○ Unstable Internet Connection
○ Etc.
class MyRoom extends Room {
async onLeave (client, consented: boolean) {
try {
if (consented) {
throw new Error("consented leave");
}
await this.allowReconnection(client, 20);
console.log("Client successfully reconnected!");
} catch (e) {
console.log("Could not reconnect.");
}
}
Colyseus
Handling reconnection (Server-side)
class MyRoom extends Room {
async onLeave (client, consented: boolean) {
try {
if (consented) {
throw new Error("consented leave");
}
await this.allowReconnection(client, 20);
console.log("Client successfully reconnected!");
} catch (e) {
console.log("Could not reconnect.");
}
}
Colyseus
Handling reconnection (Server-side)
room.leave() was
called from the
client
class MyRoom extends Room {
async onLeave (client, consented: boolean) {
try {
if (consented) {
throw new Error("consented leave");
}
await this.allowReconnection(client, 20);
console.log("Client successfully reconnected!");
} catch (e) {
console.log("Could not reconnect.");
}
}
Colyseus
Handling reconnection (Server-side)
Hold client’s seat
reservation
(sessionId)
for 20 seconds
class MyRoom extends Room {
async onLeave (client, consented: boolean) {
try {
if (consented) {
throw new Error("consented leave");
}
await this.allowReconnection(client, 20);
console.log("Client successfully reconnected!");
} catch (e) {
console.log("Could not reconnect.");
}
}
Colyseus
Handling reconnection (Server-side)
No reconnection
in 20 seconds,
promise rejected
class MyRoom extends Room {
async onLeave (client, consented: boolean) {
try {
if (consented) {
throw new Error("consented leave");
}
await this.allowReconnection(client, 20);
console.log("Client successfully reconnected!");
} catch (e) {
console.log("Could not reconnect.");
}
}
Colyseus
Handling reconnection (Server-side)
Reconnected!
const room = await client.joinOrCreate("game", {});
// Cache roomId and sessionId
localStorage.setItem("lastRoomId", room.id);
localStorage.setItem("lastSessionId", room.sessionId);
// (CLOSE BROWSER TAB)
const lastRoomId = localStorage.getItem("lastRoomId");
const lastSessionId = localStorage.getItem("lastSessionId");
const room = await client.reconnect(lastRoomId, lastSessionId);
Colyseus
Handling reconnection (Client-side)
const room = await client.joinOrCreate("game", {});
// Cache roomId and sessionId
localStorage.setItem("lastRoomId", room.id);
localStorage.setItem("lastSessionId", room.sessionId);
// (CLOSE BROWSER TAB)
const lastRoomId = localStorage.getItem("lastRoomId");
const lastSessionId = localStorage.getItem("lastSessionId");
const room = await client.reconnect(lastRoomId, lastSessionId);
Colyseus
Handling reconnection (Client-side)
Client-side Techniques
Client Server
p = [10, 10] p = [10, 10]
p = [11, 10]
p = [11, 10]
Delay
(~100ms)
Client-side Techniques
Client Server
p = [10, 10] p = [10, 10]
p = [11, 10]
p = [11, 10]
Delay
(~100ms)
Client-side Techniques
● Linear Interpolation
(Lerp)
https://mazmorra.io
tick() {
sprite.x = lerp(sprite.x, serverX, 0.07);
sprite.y = lerp(sprite.y, serverY, 0.07);
}
Client-side Techniques
● Delay to process actions
● Simulation on other client
happens 1 second later
https://poki.com/en/g/raft-wars-multiplayer
Client-side Techniques
● Client-side prediction
● Process player’s input immediately
● Enqueue player’s actions
in the server
● Process the queue at every
server tick
https://github.com/halftheopposite/tosios
Infrastructure
● How many CCU can Colyseus handle?
○ It depends!
● You have CPU and Memory limits
○ Optimize Your Game Loop
● 1000 clients in a single room?
○ Probably no!
● 1000 clients distributed across many rooms?
○ Probably yes!
Scalability ?
Scalability !
Scalability
● Colyseus’ rooms are STATEFUL
● Games are (generally) STATEFUL
● Rooms are located on a specific server and/or process.
Scalability
Colyseus
Communication between Game Rooms
onCreate() {
/**
* Pub/Sub
*/
this.presence.subscribe("global-action", (data) => {
console.log(data);
});
this.presence.publish("global-action", "hello!");
/**
* "Remote Procedure Call"
*/
matchMaker.remoteRoomCall(roomId, "methodName", [...])
}
Colyseus
Tools
@colyseus/monitor
Colyseus
Tools
@colyseus/loadtest
$ npx colyseus-loadtest bot.ts --room my_room --numClients 10
● Realtime
● Phaser/JavaScript
● Built by @tinydobbings
● ~350 concurrent players
Built with Colyseus
GunFight.io
https://gunfight.io
● Turn-based
● JavaScript version
● Defold Engine version (@selimanac)
Built with Colyseus
Tic-Tac-Toe
https://github.com/endel/colyseus-tic-tac-toe
● “The Open-Source IO Shooter”
● Realtime shooter
● PixiJS / TypeScript
● Built by @halftheopposite
Built with Colyseus
TOSIOS
https://github.com/halftheopposite/tosios
● Turn-based
● Defold Engine / LUA
● ~250 concurrent players
● (soon on mobile!)
Built with Colyseus
Raft Wars Multiplayer
https://poki.com/en/g/raft-wars-multiplayer
● Instant Game (Messenger)
● Realtime
Built with Colyseus
Chaos Shot
https://www.facebook.com/ChaosShot.io/
Plans for v1.0
● Better State Filters (on Arrays and Maps)
● Allow more sophisticated matchmaking
● Transport-independent (TCP/UDP)
● ...while keeping all client-side integrations up-to-date!
Thank You! ❤
● Twitter/GitHub: @endel

More Related Content

What's hot

What's hot (20)

Forfaiting
ForfaitingForfaiting
Forfaiting
 
Valuation of shares and valuation of goodwill
Valuation of shares and valuation of goodwillValuation of shares and valuation of goodwill
Valuation of shares and valuation of goodwill
 
Credit
CreditCredit
Credit
 
A comparative analysis of non
A comparative analysis of nonA comparative analysis of non
A comparative analysis of non
 
Factoring
FactoringFactoring
Factoring
 
Merchant banking and financial services unit i notes for mba
Merchant banking and financial services unit i notes for mbaMerchant banking and financial services unit i notes for mba
Merchant banking and financial services unit i notes for mba
 
A.loans & advances
A.loans & advancesA.loans & advances
A.loans & advances
 
Mutual funds_Financial Services
Mutual funds_Financial ServicesMutual funds_Financial Services
Mutual funds_Financial Services
 
Merchant Banking ppt
Merchant Banking pptMerchant Banking ppt
Merchant Banking ppt
 
Credit ppt
Credit pptCredit ppt
Credit ppt
 
Underwriting in Insurance
Underwriting in InsuranceUnderwriting in Insurance
Underwriting in Insurance
 
Factoring legal Aspects and Scenario in India
Factoring legal Aspects and Scenario in IndiaFactoring legal Aspects and Scenario in India
Factoring legal Aspects and Scenario in India
 
Alm objective & scope and other related matters
Alm objective & scope and other related mattersAlm objective & scope and other related matters
Alm objective & scope and other related matters
 
Banker customer relationship
Banker customer relationshipBanker customer relationship
Banker customer relationship
 
Credit rating
Credit ratingCredit rating
Credit rating
 
LITERATURE REVIEW CHIT FUND
LITERATURE REVIEW CHIT FUNDLITERATURE REVIEW CHIT FUND
LITERATURE REVIEW CHIT FUND
 
Central banks
Central banksCentral banks
Central banks
 
Personal financial planning
Personal financial planningPersonal financial planning
Personal financial planning
 
financial intermediaries and its types
financial intermediaries and its typesfinancial intermediaries and its types
financial intermediaries and its types
 
Presentation on depository participant
Presentation on depository participantPresentation on depository participant
Presentation on depository participant
 

Similar to NodeAtlanta 2020 - Making Multiplayer Games with Colyseus and Node.js.pptx

Scaling a Game Server: From 500 to 100,000 Users
Scaling a Game Server: From 500 to 100,000 UsersScaling a Game Server: From 500 to 100,000 Users
Scaling a Game Server: From 500 to 100,000 UsersGeekNightHyderabad
 
Scalability & Big Data challenges in real time multiplayer games
Scalability & Big Data challenges in real time multiplayer gamesScalability & Big Data challenges in real time multiplayer games
Scalability & Big Data challenges in real time multiplayer gamesYan Cui
 
Let s Enjoy Node.js
Let s Enjoy Node.jsLet s Enjoy Node.js
Let s Enjoy Node.jsFred Chien
 
Node lt
Node ltNode lt
Node ltsnodar
 
Defcon CTF quals
Defcon CTF qualsDefcon CTF quals
Defcon CTF qualssnyff
 
NetRacer for the Commodore 64
NetRacer for the Commodore 64NetRacer for the Commodore 64
NetRacer for the Commodore 64Leif Bloomquist
 
TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...
TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...
TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...tdc-globalcode
 
Akka for realtime multiplayer mobile games
Akka for realtime multiplayer mobile gamesAkka for realtime multiplayer mobile games
Akka for realtime multiplayer mobile gamesYan Cui
 
Mobile webapplication development
Mobile webapplication developmentMobile webapplication development
Mobile webapplication developmentGanesh Gembali
 
Realtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsRealtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsMario Gonzalez
 
Game Programming - Cloud Development
Game Programming - Cloud DevelopmentGame Programming - Cloud Development
Game Programming - Cloud DevelopmentNick Pruehs
 
Multiplayer Java Game
Multiplayer Java GameMultiplayer Java Game
Multiplayer Java Gamekarim baidar
 
Building fast,scalable game server in node.js
Building fast,scalable game server in node.jsBuilding fast,scalable game server in node.js
Building fast,scalable game server in node.jsXie ChengChao
 
Realtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsRealtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsMario Gonzalez
 
Clojure ♥ cassandra
Clojure ♥ cassandra Clojure ♥ cassandra
Clojure ♥ cassandra Max Penet
 
On Space-Scarce Economy In Blockchain Systems
On Space-Scarce Economy In Blockchain SystemsOn Space-Scarce Economy In Blockchain Systems
On Space-Scarce Economy In Blockchain SystemsDmitry Meshkov
 
Game server development in node.js in jsconf eu
Game server development in node.js in jsconf euGame server development in node.js in jsconf eu
Game server development in node.js in jsconf euXie ChengChao
 
Building Multiplayer Games (w/ Unity)
Building Multiplayer Games (w/ Unity)Building Multiplayer Games (w/ Unity)
Building Multiplayer Games (w/ Unity)Noam Gat
 

Similar to NodeAtlanta 2020 - Making Multiplayer Games with Colyseus and Node.js.pptx (20)

Scaling a Game Server: From 500 to 100,000 Users
Scaling a Game Server: From 500 to 100,000 UsersScaling a Game Server: From 500 to 100,000 Users
Scaling a Game Server: From 500 to 100,000 Users
 
Scalability & Big Data challenges in real time multiplayer games
Scalability & Big Data challenges in real time multiplayer gamesScalability & Big Data challenges in real time multiplayer games
Scalability & Big Data challenges in real time multiplayer games
 
Let s Enjoy Node.js
Let s Enjoy Node.jsLet s Enjoy Node.js
Let s Enjoy Node.js
 
Node lt
Node ltNode lt
Node lt
 
Defcon CTF quals
Defcon CTF qualsDefcon CTF quals
Defcon CTF quals
 
NetRacer for the Commodore 64
NetRacer for the Commodore 64NetRacer for the Commodore 64
NetRacer for the Commodore 64
 
TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...
TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...
TDC2017 | São Paulo - Trilha Programação Funcional How we figured out we had ...
 
Akka for realtime multiplayer mobile games
Akka for realtime multiplayer mobile gamesAkka for realtime multiplayer mobile games
Akka for realtime multiplayer mobile games
 
Mobile webapplication development
Mobile webapplication developmentMobile webapplication development
Mobile webapplication development
 
Realtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsRealtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_js
 
Game Programming - Cloud Development
Game Programming - Cloud DevelopmentGame Programming - Cloud Development
Game Programming - Cloud Development
 
Multiplayer Java Game
Multiplayer Java GameMultiplayer Java Game
Multiplayer Java Game
 
Building fast,scalable game server in node.js
Building fast,scalable game server in node.jsBuilding fast,scalable game server in node.js
Building fast,scalable game server in node.js
 
RandomGuessingGame
RandomGuessingGameRandomGuessingGame
RandomGuessingGame
 
Realtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_jsRealtime html5 multiplayer_games_with_node_js
Realtime html5 multiplayer_games_with_node_js
 
Clojure ♥ cassandra
Clojure ♥ cassandra Clojure ♥ cassandra
Clojure ♥ cassandra
 
On Space-Scarce Economy In Blockchain Systems
On Space-Scarce Economy In Blockchain SystemsOn Space-Scarce Economy In Blockchain Systems
On Space-Scarce Economy In Blockchain Systems
 
Game server development in node.js in jsconf eu
Game server development in node.js in jsconf euGame server development in node.js in jsconf eu
Game server development in node.js in jsconf eu
 
Building Multiplayer Games (w/ Unity)
Building Multiplayer Games (w/ Unity)Building Multiplayer Games (w/ Unity)
Building Multiplayer Games (w/ Unity)
 
Monte Carlo C++
Monte Carlo C++Monte Carlo C++
Monte Carlo C++
 

Recently uploaded

ONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdf
ONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdfONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdf
ONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdfKamal Acharya
 
Construction method of steel structure space frame .pptx
Construction method of steel structure space frame .pptxConstruction method of steel structure space frame .pptx
Construction method of steel structure space frame .pptxwendy cai
 
Paint shop management system project report.pdf
Paint shop management system project report.pdfPaint shop management system project report.pdf
Paint shop management system project report.pdfKamal Acharya
 
Intelligent Agents, A discovery on How A Rational Agent Acts
Intelligent Agents, A discovery on How A Rational Agent ActsIntelligent Agents, A discovery on How A Rational Agent Acts
Intelligent Agents, A discovery on How A Rational Agent ActsSheetal Jain
 
Quiz application system project report..pdf
Quiz application system project report..pdfQuiz application system project report..pdf
Quiz application system project report..pdfKamal Acharya
 
Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...
Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...
Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...Lovely Professional University
 
Filters for Electromagnetic Compatibility Applications
Filters for Electromagnetic Compatibility ApplicationsFilters for Electromagnetic Compatibility Applications
Filters for Electromagnetic Compatibility ApplicationsMathias Magdowski
 
Multivibrator and its types defination and usges.pptx
Multivibrator and its types defination and usges.pptxMultivibrator and its types defination and usges.pptx
Multivibrator and its types defination and usges.pptxalijaker017
 
"United Nations Park" Site Visit Report.
"United Nations Park" Site  Visit Report."United Nations Park" Site  Visit Report.
"United Nations Park" Site Visit Report.MdManikurRahman
 
Diploma Engineering Drawing Qp-2024 Ece .pdf
Diploma Engineering Drawing Qp-2024 Ece .pdfDiploma Engineering Drawing Qp-2024 Ece .pdf
Diploma Engineering Drawing Qp-2024 Ece .pdfJNTUA
 
RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5
RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5
RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5T.D. Shashikala
 
Natalia Rutkowska - BIM School Course in Kraków
Natalia Rutkowska - BIM School Course in KrakówNatalia Rutkowska - BIM School Course in Kraków
Natalia Rutkowska - BIM School Course in Krakówbim.edu.pl
 
How to Design and spec harmonic filter.pdf
How to Design and spec harmonic filter.pdfHow to Design and spec harmonic filter.pdf
How to Design and spec harmonic filter.pdftawat puangthong
 
ROAD CONSTRUCTION PRESENTATION.PPTX.pptx
ROAD CONSTRUCTION PRESENTATION.PPTX.pptxROAD CONSTRUCTION PRESENTATION.PPTX.pptx
ROAD CONSTRUCTION PRESENTATION.PPTX.pptxGagandeepKaur617299
 
Low rpm Generator for efficient energy harnessing from a two stage wind turbine
Low rpm Generator for efficient energy harnessing from a two stage wind turbineLow rpm Generator for efficient energy harnessing from a two stage wind turbine
Low rpm Generator for efficient energy harnessing from a two stage wind turbineAftabkhan575376
 
2024 DevOps Pro Europe - Growing at the edge
2024 DevOps Pro Europe - Growing at the edge2024 DevOps Pro Europe - Growing at the edge
2024 DevOps Pro Europe - Growing at the edgePaco Orozco
 
E-Commerce Shopping using MERN Stack where different modules are present
E-Commerce Shopping using MERN Stack where different modules are presentE-Commerce Shopping using MERN Stack where different modules are present
E-Commerce Shopping using MERN Stack where different modules are presentjatinraor66
 
Object Oriented Programming OOP Lab Manual.docx
Object Oriented Programming OOP Lab Manual.docxObject Oriented Programming OOP Lab Manual.docx
Object Oriented Programming OOP Lab Manual.docxRashidFaridChishti
 
The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...
The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...
The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...Roi Lipman
 
ChatGPT Prompt Engineering for project managers.pdf
ChatGPT Prompt Engineering for project managers.pdfChatGPT Prompt Engineering for project managers.pdf
ChatGPT Prompt Engineering for project managers.pdfqasastareekh
 

Recently uploaded (20)

ONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdf
ONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdfONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdf
ONLINE VEHICLE RENTAL SYSTEM PROJECT REPORT.pdf
 
Construction method of steel structure space frame .pptx
Construction method of steel structure space frame .pptxConstruction method of steel structure space frame .pptx
Construction method of steel structure space frame .pptx
 
Paint shop management system project report.pdf
Paint shop management system project report.pdfPaint shop management system project report.pdf
Paint shop management system project report.pdf
 
Intelligent Agents, A discovery on How A Rational Agent Acts
Intelligent Agents, A discovery on How A Rational Agent ActsIntelligent Agents, A discovery on How A Rational Agent Acts
Intelligent Agents, A discovery on How A Rational Agent Acts
 
Quiz application system project report..pdf
Quiz application system project report..pdfQuiz application system project report..pdf
Quiz application system project report..pdf
 
Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...
Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...
Activity Planning: Objectives, Project Schedule, Network Planning Model. Time...
 
Filters for Electromagnetic Compatibility Applications
Filters for Electromagnetic Compatibility ApplicationsFilters for Electromagnetic Compatibility Applications
Filters for Electromagnetic Compatibility Applications
 
Multivibrator and its types defination and usges.pptx
Multivibrator and its types defination and usges.pptxMultivibrator and its types defination and usges.pptx
Multivibrator and its types defination and usges.pptx
 
"United Nations Park" Site Visit Report.
"United Nations Park" Site  Visit Report."United Nations Park" Site  Visit Report.
"United Nations Park" Site Visit Report.
 
Diploma Engineering Drawing Qp-2024 Ece .pdf
Diploma Engineering Drawing Qp-2024 Ece .pdfDiploma Engineering Drawing Qp-2024 Ece .pdf
Diploma Engineering Drawing Qp-2024 Ece .pdf
 
RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5
RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5
RM&IPR M5 notes.pdfResearch Methodolgy & Intellectual Property Rights Series 5
 
Natalia Rutkowska - BIM School Course in Kraków
Natalia Rutkowska - BIM School Course in KrakówNatalia Rutkowska - BIM School Course in Kraków
Natalia Rutkowska - BIM School Course in Kraków
 
How to Design and spec harmonic filter.pdf
How to Design and spec harmonic filter.pdfHow to Design and spec harmonic filter.pdf
How to Design and spec harmonic filter.pdf
 
ROAD CONSTRUCTION PRESENTATION.PPTX.pptx
ROAD CONSTRUCTION PRESENTATION.PPTX.pptxROAD CONSTRUCTION PRESENTATION.PPTX.pptx
ROAD CONSTRUCTION PRESENTATION.PPTX.pptx
 
Low rpm Generator for efficient energy harnessing from a two stage wind turbine
Low rpm Generator for efficient energy harnessing from a two stage wind turbineLow rpm Generator for efficient energy harnessing from a two stage wind turbine
Low rpm Generator for efficient energy harnessing from a two stage wind turbine
 
2024 DevOps Pro Europe - Growing at the edge
2024 DevOps Pro Europe - Growing at the edge2024 DevOps Pro Europe - Growing at the edge
2024 DevOps Pro Europe - Growing at the edge
 
E-Commerce Shopping using MERN Stack where different modules are present
E-Commerce Shopping using MERN Stack where different modules are presentE-Commerce Shopping using MERN Stack where different modules are present
E-Commerce Shopping using MERN Stack where different modules are present
 
Object Oriented Programming OOP Lab Manual.docx
Object Oriented Programming OOP Lab Manual.docxObject Oriented Programming OOP Lab Manual.docx
Object Oriented Programming OOP Lab Manual.docx
 
The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...
The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...
The battle for RAG, explore the pros and cons of using KnowledgeGraphs and Ve...
 
ChatGPT Prompt Engineering for project managers.pdf
ChatGPT Prompt Engineering for project managers.pdfChatGPT Prompt Engineering for project managers.pdf
ChatGPT Prompt Engineering for project managers.pdf
 

NodeAtlanta 2020 - Making Multiplayer Games with Colyseus and Node.js.pptx

  • 1. Making Online Multiplayer Game Experiences With Colyseus and Node.js This presentation is from Jan 2020. It was supposed to happen on Node Atlanta 2020, but unfortunately it got cancelled.
  • 2. Hi, I’m Endel 👋 👨💻 Software Engineer and Game Developer from Brazil 🎓 Game Development & Digital Entertainment GitHub / Twitter: @endel I DON’T LIKE SOCCER
  • 3. Summary ● Introduction ● Basic concepts ○ How Online Multiplayer Games Work ○ Authoritative Game Servers ● Colyseus in-depth ○ Match-making ○ Game State & Serialization ○ Infrastructure ● Client-side Techniques
  • 4. A naive beginning (2015) ● Socket.io ● Not authoritative ● Synchronization issues between clients
  • 5. Server Broadcasts messages back to clients Client 1. State Client 2. State Client 3. State 100ms 100ms 150ms A naive beginning (2015) Not authoritative
  • 6. A naive beginning (2015) All I wanted ● Be able to handle multiple game sessions ● Manipulate data structures in the server ○ Have them automatically synchronized with the clients
  • 8. Why Colyseus? ● Multiplayer games should be easier to make ● Should be more accessible ● Should work on as many platforms as possible ● Should be open-source!
  • 9. Client-side Integration ● HTML5 (JavaScript / TypeScript) ● Unity3D (C#) ● Defold Engine (LUA) ● Haxe ● Cocos2d-X (C++)
  • 10. How Online Multiplayer Games Work? ● Peer-to-peer ● Client as a host ● Hybrid (of two above) ● Client / Server Server Dumb client Dumb client Dumb client
  • 11. Responsibilities of the Server ● Hold the Game State ● Hold the Game Logic ● Validate client inputs ● Send the Game State to the clients Server Dumb client Dumb client Dumb client
  • 12. Responsibilities of the Client ● Visual representation ● Apply game state updates in a pleasant manner ● Send inputs (actions) to the Server Server Dumb client Dumb client Dumb client
  • 13. Authoritative Game Servers My new position is now [x, y] *mxlvl.com: This game was made by @x100
  • 14. Authoritative Game Servers My new position is now [x, y] *mxlvl.com: This game was made by @x100
  • 15. Authoritative Game Servers I’m pointing at angle X and moving forward *mxlvl.com: This game was made by @x100
  • 16. Authoritative Game Servers I’m pointing at angle X and moving forward *mxlvl.com: This game was made by @x100
  • 17. Colyseus ● Uses HTTP/WebSockets ● Game Rooms ● Match-making ● Room State Serialization ● Scalability*
  • 19. Colyseus Game Rooms The match-maker has instantiated this room. Rooms are in-memory, and live on a particular Node.js process
  • 20. Colyseus Game Rooms Client is asking to join into this room
  • 21. Colyseus Game Rooms Client sent a message to this room
  • 22. Colyseus Game Rooms Client has been disconnected (or left intentionally)
  • 23. Colyseus Game Rooms The room is being destroyed. (It’s a good place to persist things on the database) Rooms are disposed automatically when the last client disconnects (unless autoDispose=false)
  • 26. Colyseus Game Rooms ● Rooms are created during matchmaking ● A client can be connected to multiple rooms ● Each room connection has its own WebSocket connection
  • 27. ● client.join( “game”, { options } ) ● client.create( “game”, { options } ) ● client.joinOrCreate( “game”, { options } ) ● client.joinById( roomId, { options } ) ● client.reconnect( roomId, sessionId ) Colyseus Matchmaking (Client-side)
  • 28. Colyseus Matchmaking (Client-side) try { const room = await client.joinOrCreate("game", { name: "Jake Badlands" }); } catch (e) { console.log("Could not join!"); console.error(e); }
  • 29. Colyseus Matchmaking ient.joinOrCreate("game", { s" t join!"); class MyRoom extends Room { maxClients = 4; async onAuth(client, options) { // retrieve user data from auth provider return true; } onJoin(client, options) { // mutate the this.state console.log(options.name, "joined!"); } } Client-side: Server-side:
  • 30. http: request seat reservation roomId / sessionId WebSocket request Connection established Colyseus Matchmaking
  • 31. Full Room’s State ... State patches Connection established Colyseus Room State & Serialization ... State patches
  • 32. ● The Room State is MUTABLE ● Patches are broadcasted at every 50ms ○ Customizable via this.setPatchRate(ms) ( But first, a bit of a background… ) Colyseus Room State & Serialization
  • 33. 👎 Serialization: back in v0.1 ~ v0.3 👎 ● Deep JavaScript Object Comparison ● JSON Patch (RFC6902) ● Patch size is too large [ { "op": "remove", "path": "/players/N150OHMve" }, { "op": "add", "path": "/players/NkfeqSGPx", "value": {"x": 10, "y": 10} }, { "op": "replace", "path": "/players/NkfeqSGPx/x", "value": 5 } ] Previous State ⇔ Current State
  • 34. 👎 Serialization: back in v0.4 ~ v0.9 👎 ● “Fossil Delta” Binary Diff Compression ● Hard to detect a change in the state ● Creates a new copy when decoding var currentState = this.state; var currentStateEncoded = msgpack.encode( currentState ); // skip if state has not changed. if ( currentStateEncoded.equals( this._previousStateEncoded ) ) { return false; } var patches = delta.create(this._previousStateEncoded, currentStateEncoded); CPU Intensive
  • 35. Serialization: v0.10+ ● @colyseus/schema ● Schema-based ● Strongly-typed ● Binary serialization ● Inspired by protobuf / flatbuffers / avro
  • 36. Serialization: v0.10+ ● @colyseus/schema ● Schema-based ● Strongly-typed ● Binary serialization ● Inspired by protobuf / flatbuffers / avro
  • 37. 🔲 Encode/Decode only fields that have changed 🔲 No bottleneck to detect state changes 🔲 Mutations should be cheap 🔲 Avoid decoding large objects that haven't been patched 🔲 Better developer experience on statically-typed languages (C#, C++) Serialization: v0.10+ Checklist
  • 38. ✅ Encode/Decode only fields that have changed ✅ No bottleneck to detect state changes ❓ Mutations should be cheap (need more benchmarks, so far seems ✅) ✅ Avoid decoding large objects that haven't been patched ✅ Better developer experience on statically-typed languages (C#, C++) Serialization: v0.10+ Checklist
  • 39. class Player extends Schema { @type("number") x: number; @type("number") y: number; } const player = new Player(); player.x = 10; player.y = 20; player.encode() [ 0, 10, 1, 20 ] Definition Usage Serialization: v0.10+ Demo
  • 40. class Player extends Schema { @type("number") x: number; @type("number") y: number; } const player = new Player(); player.x = 10; player.y = 20; player.encode() [ 0, 10, 1, 20 ] Definition Usage Serialization: v0.10+ Demo
  • 41. class Player extends Schema { @type("number") x: number; @type("number") y: number; } const player = new Player(); player.x = 10; player.y = 20; player.encode() [ 0, 10, 1, 20 ] Definition Usage Serialization: v0.10+ Demo
  • 43. Colyseus Room State & Serialization import { Schema, type, MapSchema } from "@colyseus/schema"; class Player extends Schema { @type("number") position: number = 0; } class GameState extends Schema { @type({ map: Player }) players = new MapSchema<Player>(); } Game State Example
  • 44. class MyRoom extends Room { onCreate(options) { this.setState(new GameState()); } onJoin(client, options) { this.state.players[client.sessionId] = new Player(); } onMessage(client, message) { if (message.move) { this.state.players[client.sessionId].position++; } Colyseus Room State & Serialization Room State Example
  • 45. Colyseus Room State & Serialization const room = await client.joinOrCreate("game"); room.onStateChange((state) => { console.log("You are:", state.players[ room.sessionId ]) }); Client-side: Receiving Patches
  • 46. Colyseus Room State & Serialization const room = await client.joinOrCreate("game"); room.onStateChange((state) => { console.log("You are:", state.players[ room.sessionId ]) }); Client-side: Receiving Patches What exactly has changed?
  • 47. Colyseus Room State & Serialization const room = await client.joinOrCreate("game"); room.state.players.onAdd = (player, sessionId) => { console.log("player joined", sessionId); player.listen("position", (position) => { console.log("position changed", position); }); } room.state.players.onRemove = (player, sessionId) { console.log("player left", sessionId); }
  • 48. Colyseus Room State & Serialization const room = await client.joinOrCreate("game"); room.state.players.onAdd = (player, sessionId) => { console.log("player joined", sessionId); player.listen("position", (position) => { console.log("position changed", position); }); } room.state.players.onRemove = (player, sessionId) { console.log("player left", sessionId); } Player joined Player left
  • 49. Colyseus Room State & Serialization const room = await client.joinOrCreate("game"); room.state.players.onAdd = (player, sessionId) => { console.log("player joined", sessionId); player.listen("position", (position) => { console.log("position changed", position); }); } room.state.players.onRemove = (player, sessionId) { console.log("player left", sessionId); } Particular property changes
  • 50. Colyseus Room State & Serialization const room = await client.joinOrCreate<GameState>("game"); room.state.players.onAdd = (player, sessionId) => { console.log("player joined", sessionId); player.listen("position", (position) => { console.log("position changed", position); }); } room.state.players.onRemove = (player, sessionId) { console.log("player left", sessionId); } Autocompletion 💖 TypeScript 💖
  • 51. Colyseus Room State & Serialization const room = await client.joinOrCreate("game"); room.state.players.onAdd = (player, sessionId) => { console.log("player joined", sessionId); player.listen("position", (position) => { console.log("position changed", position); }); } room.state.players.onRemove = (player, sessionId) { console.log("player left", sessionId); } (Consistency?!)
  • 52. ● By default, the entire state is sent to everyone ○ This way no “private” data can be stored in the state ● @filter() let you define a filter callback per property, per client Colyseus State Filters! (Experimental)
  • 53. Colyseus State Filters! (Experimental) class Entity extends Schema { @filter(function(client, value, root) { // this = instance of Entity // client = the client which this value is being filtered for // value = `secret` value // root = the Root State return false; }) @type("number") secret: number; }
  • 54. Colyseus State Filters! (Experimental) class Entity extends Schema { @filter(function(client, value, root) { // this = instance of Entity // client = the client which this value is being filtered for // value = `secret` value // root = the Root State return false; }) @type("number") secret: number; }
  • 55. Colyseus State Filters! (Experimental) class Entity extends Schema { @filter(function(client, value, root) { // this = instance of Entity // client = the client which this value is being filtered for // value = `secret` value // root = the Root State return false; }) @type("number") secret: number; }
  • 56. Colyseus State Filters! (Experimental) class Card extends Schema { @filter(function(this: Card, client, value, root?: RootState) { return root. players[client.sessionId]. cards.indexOf(this) !== -1; }) @type("number") number: number; } Card Game Example: Only Card owners can see their own Card data
  • 57. Colyseus State Filters! (Experimental) class Player extends Schema { sessionId: string; @filter(function(this: Player, client: any, value: Card) { return this.sessionId === client.sessionId; }) @type([Card]) cards = new ArraySchema<Card>(); } ⚠️ Array and Map filters are not currently supported ⚠️ (Planned for version 1.0)
  • 58. Colyseus State Filters! (Experimental) class State extends Schema { @filter(function(this: State, client: any, value: Player) { const player = this.players[client.sessionId] const a = value.x - player.x; const b = value.y - player.y; return (Math.sqrt(a * a + b * b)) <= 10; }) @type({ map: Player }) players = new MapSchema<Player>(); } ⚠️ Array and Map filters are not currently supported ⚠️ (Planned for version 1.0)
  • 59. using Colyseus.Schema; public class State : Schema { [Type("string")] public string fieldString = ""; [Type("number")] public float fieldNumber = 0; [Type("ref", typeof(Player))] public Player player = new Player( [Type("array", typeof(ArraySchema< public ArraySchema<Player> arrayOf ArraySchema<Player>(); [Type("map", typeof(MapSchema<Play Client-side integration (C#) ● npx schema-codegen State.ts --csharp --output client-state generated: State.cs Colyseus Room State & Serialization
  • 60. using namespace colyseus::schema; class State : public Schema { public: string fieldString = ""; varint_t fieldNumber = 0; Player *player = new Player(); ArraySchema<Player*> *arrayOfPlaye ArraySchema<Player*>(); MapSchema<Player*> *mapOfPlayers = State() { this->_indexes = {{0, "fiel this->_types = {{0, "strin this->_childPrimitiveTypes Client-side integration (C++) ● npx schema-codegen State.ts --cpp --output client-state generated: State.hpp Colyseus Room State & Serialization
  • 61. import io.colyseus.serializer.schema.Schema; class State extends Schema { @:type("string") public var fieldString: String = "" @:type("number") public var fieldNumber: Dynamic = 0 @:type("ref", Player) public var player: Player = new Pla @:type("array", Player) public var arrayOfPlayers: ArraySch Client-side integration (Haxe) ● npx schema-codegen State.ts --haxe --output client-state generated: State.hx Colyseus Room State & Serialization
  • 62. Colyseus Sending Messages ● Messages use MsgPack by default room.onMessage((message) => { console.log("received:", message) }) room.send({ action: "hello" }) Client-side onMessage(client, message) { if (message.action === "hello") { this.broadcast("world!"); this.send(client, "world!"); } } Server-side
  • 63. Colyseus Sending Messages ● Messages use MsgPack by default room.onMessage((message) => { console.log("received:", message) }) room.send({ action: "hello" }) Client-side onMessage(client, message) { if (message.action === "hello") { this.broadcast("world!"); this.send(client, "world!"); } } Server-side Send message to everyone
  • 64. Colyseus Sending Messages ● Messages use MsgPack by default room.onMessage((message) => { console.log("received:", message) }) room.send({ action: "hello" }) Client-side onMessage(client, message) { if (message.action === "hello") { this.broadcast("world!"); this.send(client, "world!"); } } Server-side Send message to a single client
  • 65. Colyseus Handling reconnection ● What if one of my clients drops the connection? ○ Closed/Refreshed Browser ○ Switched from Wifi / 3G ○ Unstable Internet Connection ○ Etc.
  • 66. class MyRoom extends Room { async onLeave (client, consented: boolean) { try { if (consented) { throw new Error("consented leave"); } await this.allowReconnection(client, 20); console.log("Client successfully reconnected!"); } catch (e) { console.log("Could not reconnect."); } } Colyseus Handling reconnection (Server-side)
  • 67. class MyRoom extends Room { async onLeave (client, consented: boolean) { try { if (consented) { throw new Error("consented leave"); } await this.allowReconnection(client, 20); console.log("Client successfully reconnected!"); } catch (e) { console.log("Could not reconnect."); } } Colyseus Handling reconnection (Server-side) room.leave() was called from the client
  • 68. class MyRoom extends Room { async onLeave (client, consented: boolean) { try { if (consented) { throw new Error("consented leave"); } await this.allowReconnection(client, 20); console.log("Client successfully reconnected!"); } catch (e) { console.log("Could not reconnect."); } } Colyseus Handling reconnection (Server-side) Hold client’s seat reservation (sessionId) for 20 seconds
  • 69. class MyRoom extends Room { async onLeave (client, consented: boolean) { try { if (consented) { throw new Error("consented leave"); } await this.allowReconnection(client, 20); console.log("Client successfully reconnected!"); } catch (e) { console.log("Could not reconnect."); } } Colyseus Handling reconnection (Server-side) No reconnection in 20 seconds, promise rejected
  • 70. class MyRoom extends Room { async onLeave (client, consented: boolean) { try { if (consented) { throw new Error("consented leave"); } await this.allowReconnection(client, 20); console.log("Client successfully reconnected!"); } catch (e) { console.log("Could not reconnect."); } } Colyseus Handling reconnection (Server-side) Reconnected!
  • 71. const room = await client.joinOrCreate("game", {}); // Cache roomId and sessionId localStorage.setItem("lastRoomId", room.id); localStorage.setItem("lastSessionId", room.sessionId); // (CLOSE BROWSER TAB) const lastRoomId = localStorage.getItem("lastRoomId"); const lastSessionId = localStorage.getItem("lastSessionId"); const room = await client.reconnect(lastRoomId, lastSessionId); Colyseus Handling reconnection (Client-side)
  • 72. const room = await client.joinOrCreate("game", {}); // Cache roomId and sessionId localStorage.setItem("lastRoomId", room.id); localStorage.setItem("lastSessionId", room.sessionId); // (CLOSE BROWSER TAB) const lastRoomId = localStorage.getItem("lastRoomId"); const lastSessionId = localStorage.getItem("lastSessionId"); const room = await client.reconnect(lastRoomId, lastSessionId); Colyseus Handling reconnection (Client-side)
  • 73. Client-side Techniques Client Server p = [10, 10] p = [10, 10] p = [11, 10] p = [11, 10] Delay (~100ms)
  • 74. Client-side Techniques Client Server p = [10, 10] p = [10, 10] p = [11, 10] p = [11, 10] Delay (~100ms)
  • 75. Client-side Techniques ● Linear Interpolation (Lerp) https://mazmorra.io tick() { sprite.x = lerp(sprite.x, serverX, 0.07); sprite.y = lerp(sprite.y, serverY, 0.07); }
  • 76. Client-side Techniques ● Delay to process actions ● Simulation on other client happens 1 second later https://poki.com/en/g/raft-wars-multiplayer
  • 77. Client-side Techniques ● Client-side prediction ● Process player’s input immediately ● Enqueue player’s actions in the server ● Process the queue at every server tick https://github.com/halftheopposite/tosios
  • 78. Infrastructure ● How many CCU can Colyseus handle? ○ It depends! ● You have CPU and Memory limits ○ Optimize Your Game Loop ● 1000 clients in a single room? ○ Probably no! ● 1000 clients distributed across many rooms? ○ Probably yes!
  • 81. Scalability ● Colyseus’ rooms are STATEFUL ● Games are (generally) STATEFUL ● Rooms are located on a specific server and/or process.
  • 83. Colyseus Communication between Game Rooms onCreate() { /** * Pub/Sub */ this.presence.subscribe("global-action", (data) => { console.log(data); }); this.presence.publish("global-action", "hello!"); /** * "Remote Procedure Call" */ matchMaker.remoteRoomCall(roomId, "methodName", [...]) }
  • 85. Colyseus Tools @colyseus/loadtest $ npx colyseus-loadtest bot.ts --room my_room --numClients 10
  • 86. ● Realtime ● Phaser/JavaScript ● Built by @tinydobbings ● ~350 concurrent players Built with Colyseus GunFight.io https://gunfight.io
  • 87. ● Turn-based ● JavaScript version ● Defold Engine version (@selimanac) Built with Colyseus Tic-Tac-Toe https://github.com/endel/colyseus-tic-tac-toe
  • 88. ● “The Open-Source IO Shooter” ● Realtime shooter ● PixiJS / TypeScript ● Built by @halftheopposite Built with Colyseus TOSIOS https://github.com/halftheopposite/tosios
  • 89. ● Turn-based ● Defold Engine / LUA ● ~250 concurrent players ● (soon on mobile!) Built with Colyseus Raft Wars Multiplayer https://poki.com/en/g/raft-wars-multiplayer
  • 90. ● Instant Game (Messenger) ● Realtime Built with Colyseus Chaos Shot https://www.facebook.com/ChaosShot.io/
  • 91. Plans for v1.0 ● Better State Filters (on Arrays and Maps) ● Allow more sophisticated matchmaking ● Transport-independent (TCP/UDP) ● ...while keeping all client-side integrations up-to-date!
  • 92. Thank You! ❤ ● Twitter/GitHub: @endel

Editor's Notes

  1. This game was made using Colyseus by @x100, a community member
  2. This is how a room definition looks like All these methods can be async
  3. I’ll talk a bit about state serialization before going into handling the game state I hope it’s not going to be too much information, because it’s kind of a deep topic
  4. Fossil Delta is really good for small binary diffs CPU Intensive detecting changes Explain client-side new allocations and garbage collection
  5. Why would you re-implement these great tools Incremental encoding
  6. This is the checklist I had before going ahead and implementing the serializer
  7. Because we’re using Proxies to be able to catch changes on the state, mutations have this cost
  8. The .encode() method is just to illustrate how it works, you usually don’t need to call it manually, as the room does automatically at the patch interval
  9. The .encode() method is just to illustrate how it works, you usually don’t need to call it manually, as the room does automatically at the patch interval
  10. The .encode() method is just to illustrate how it works, you usually don’t need to call it manually, as the room does automatically at the patch interval
  11. Has been released early last year I’ve a good chunk of time last year fixing bugs on it, and improving things
  12. I’ll talk a bit about state serialization before going into handling the game state
  13. During onStateChange, you can’t really know exactly what has changed
  14. During onStateChange, you can’t really know exactly what has changed
  15. Mention that “player” references can be passed around, as it is going to be always the same
  16. Here you actually get autocompletion for the field, and the callback will have the right type you’re listening for
  17. Filters are experimental, because their performance is not that great currently
  18. This is the signature of the @filter callback
  19. Delays are
  20. This is a trickier one. Mention turn-based
  21. This is a trickier one.