SlideShare uma empresa Scribd logo
1 de 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

Mais conteúdo relacionado

Mais procurados

Two dimensional viewing
Two dimensional viewingTwo dimensional viewing
Two dimensional viewing
Mohd Arif
 

Mais procurados (20)

Conditional jump
Conditional jumpConditional jump
Conditional jump
 
2D Transformation in Computer Graphics
2D Transformation in Computer Graphics2D Transformation in Computer Graphics
2D Transformation in Computer Graphics
 
Computer graphics basic transformation
Computer graphics basic transformationComputer graphics basic transformation
Computer graphics basic transformation
 
sum of subset problem using Backtracking
sum of subset problem using Backtrackingsum of subset problem using Backtracking
sum of subset problem using Backtracking
 
Operator Precedence Grammar
Operator Precedence GrammarOperator Precedence Grammar
Operator Precedence Grammar
 
Chapter 11 - Sorting and Searching
Chapter 11 - Sorting and SearchingChapter 11 - Sorting and Searching
Chapter 11 - Sorting and Searching
 
Wireless transmission
Wireless transmissionWireless transmission
Wireless transmission
 
Dijkstra & flooding ppt(Routing algorithm)
Dijkstra & flooding ppt(Routing algorithm)Dijkstra & flooding ppt(Routing algorithm)
Dijkstra & flooding ppt(Routing algorithm)
 
B trees
B treesB trees
B trees
 
Hashing
HashingHashing
Hashing
 
simple problem to convert NFA with epsilon to without epsilon
simple problem to convert NFA with epsilon to without epsilonsimple problem to convert NFA with epsilon to without epsilon
simple problem to convert NFA with epsilon to without epsilon
 
3D transformation and viewing
3D transformation and viewing3D transformation and viewing
3D transformation and viewing
 
Recursion (left recursion) | Compiler design
Recursion (left recursion) | Compiler designRecursion (left recursion) | Compiler design
Recursion (left recursion) | Compiler design
 
Histogram processing
Histogram processingHistogram processing
Histogram processing
 
Discuss the scrollable result set in jdbc
Discuss the scrollable result set in jdbcDiscuss the scrollable result set in jdbc
Discuss the scrollable result set in jdbc
 
Clipping
ClippingClipping
Clipping
 
Homogeneous Representation: rotating, shearing
Homogeneous Representation: rotating, shearingHomogeneous Representation: rotating, shearing
Homogeneous Representation: rotating, shearing
 
Two dimensional viewing
Two dimensional viewingTwo dimensional viewing
Two dimensional viewing
 
What is sparse matrix
What is sparse matrixWhat is sparse matrix
What is sparse matrix
 
Boolean
BooleanBoolean
Boolean
 

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

Mobile webapplication development
Mobile webapplication developmentMobile webapplication development
Mobile webapplication development
Ganesh 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_js
Mario Gonzalez
 

Semelhante a 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++
 

Último

Maher Othman Interior Design Portfolio..
Maher Othman Interior Design Portfolio..Maher Othman Interior Design Portfolio..
Maher Othman Interior Design Portfolio..
MaherOthman7
 
Online crime reporting system project.pdf
Online crime reporting system project.pdfOnline crime reporting system project.pdf
Online crime reporting system project.pdf
Kamal Acharya
 
Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...
Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...
Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...
drjose256
 

Último (20)

Maher Othman Interior Design Portfolio..
Maher Othman Interior Design Portfolio..Maher Othman Interior Design Portfolio..
Maher Othman Interior Design Portfolio..
 
5G and 6G refer to generations of mobile network technology, each representin...
5G and 6G refer to generations of mobile network technology, each representin...5G and 6G refer to generations of mobile network technology, each representin...
5G and 6G refer to generations of mobile network technology, each representin...
 
Insurance management system project report.pdf
Insurance management system project report.pdfInsurance management system project report.pdf
Insurance management system project report.pdf
 
Online crime reporting system project.pdf
Online crime reporting system project.pdfOnline crime reporting system project.pdf
Online crime reporting system project.pdf
 
Fabrication Of Automatic Star Delta Starter Using Relay And GSM Module By Utk...
Fabrication Of Automatic Star Delta Starter Using Relay And GSM Module By Utk...Fabrication Of Automatic Star Delta Starter Using Relay And GSM Module By Utk...
Fabrication Of Automatic Star Delta Starter Using Relay And GSM Module By Utk...
 
Quiz application system project report..pdf
Quiz application system project report..pdfQuiz application system project report..pdf
Quiz application system project report..pdf
 
Instruct Nirmaana 24-Smart and Lean Construction Through Technology.pdf
Instruct Nirmaana 24-Smart and Lean Construction Through Technology.pdfInstruct Nirmaana 24-Smart and Lean Construction Through Technology.pdf
Instruct Nirmaana 24-Smart and Lean Construction Through Technology.pdf
 
Lesson no16 application of Induction Generator in Wind.ppsx
Lesson no16 application of Induction Generator in Wind.ppsxLesson no16 application of Induction Generator in Wind.ppsx
Lesson no16 application of Induction Generator in Wind.ppsx
 
Research Methodolgy & Intellectual Property Rights Series 1
Research Methodolgy & Intellectual Property Rights Series 1Research Methodolgy & Intellectual Property Rights Series 1
Research Methodolgy & Intellectual Property Rights Series 1
 
Research Methodolgy & Intellectual Property Rights Series 2
Research Methodolgy & Intellectual Property Rights Series 2Research Methodolgy & Intellectual Property Rights Series 2
Research Methodolgy & Intellectual Property Rights Series 2
 
"United Nations Park" Site Visit Report.
"United Nations Park" Site  Visit Report."United Nations Park" Site  Visit Report.
"United Nations Park" Site Visit Report.
 
Piping and instrumentation diagram p.pdf
Piping and instrumentation diagram p.pdfPiping and instrumentation diagram p.pdf
Piping and instrumentation diagram p.pdf
 
NEWLETTER FRANCE HELICES/ SDS SURFACE DRIVES - MAY 2024
NEWLETTER FRANCE HELICES/ SDS SURFACE DRIVES - MAY 2024NEWLETTER FRANCE HELICES/ SDS SURFACE DRIVES - MAY 2024
NEWLETTER FRANCE HELICES/ SDS SURFACE DRIVES - MAY 2024
 
SLIDESHARE PPT-DECISION MAKING METHODS.pptx
SLIDESHARE PPT-DECISION MAKING METHODS.pptxSLIDESHARE PPT-DECISION MAKING METHODS.pptx
SLIDESHARE PPT-DECISION MAKING METHODS.pptx
 
Involute of a circle,Square, pentagon,HexagonInvolute_Engineering Drawing.pdf
Involute of a circle,Square, pentagon,HexagonInvolute_Engineering Drawing.pdfInvolute of a circle,Square, pentagon,HexagonInvolute_Engineering Drawing.pdf
Involute of a circle,Square, pentagon,HexagonInvolute_Engineering Drawing.pdf
 
analog-vs-digital-communication (concept of analog and digital).pptx
analog-vs-digital-communication (concept of analog and digital).pptxanalog-vs-digital-communication (concept of analog and digital).pptx
analog-vs-digital-communication (concept of analog and digital).pptx
 
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
 
Module-III Varried Flow.pptx GVF Definition, Water Surface Profile Dynamic Eq...
Module-III Varried Flow.pptx GVF Definition, Water Surface Profile Dynamic Eq...Module-III Varried Flow.pptx GVF Definition, Water Surface Profile Dynamic Eq...
Module-III Varried Flow.pptx GVF Definition, Water Surface Profile Dynamic Eq...
 
BORESCOPE INSPECTION for engins CFM56.pdf
BORESCOPE INSPECTION for engins CFM56.pdfBORESCOPE INSPECTION for engins CFM56.pdf
BORESCOPE INSPECTION for engins CFM56.pdf
 
Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...
Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...
Tembisa Central Terminating Pills +27838792658 PHOMOLONG Top Abortion Pills F...
 

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

Notas do Editor

  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.