├── .editorconfig ├── .env.development ├── .env.production ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── app.json ├── ecosystem.config.js ├── package.json ├── src ├── app.config.ts ├── config │ └── auth.ts ├── index.ts ├── rooms │ ├── 01-chat-room.ts │ ├── 02-state-handler.ts │ ├── 03-auth.ts │ ├── 04-reconnection.ts │ └── 07-custom-lobby-room.ts └── static │ ├── 01-chat.html │ ├── 02-state-handler.html │ ├── 03-auth.html │ ├── 04-reconnection.html │ ├── 05-lobby-room.html │ ├── 06-relay-room.html │ └── 07-custom-lobby-room.html └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | charset = utf-8 4 | trim_trailing_whitespace = true 5 | indent_style = space 6 | indent_size = 4 7 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | 3 | JWT_SECRET=verysafe 4 | AUTH_SALT=salt 5 | SESSION_SECRET=secret 6 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | NODE_ENV=production -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | package-lock.json 4 | npm-debug.log 5 | build 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug", 6 | "type": "pwa-node", 7 | "request": "launch", 8 | "runtimeExecutable": "node", 9 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"], 10 | "args": ["src/index.ts"], 11 | "cwd": "${workspaceRoot}", 12 | "internalConsoleOptions": "openOnSessionStart", 13 | "resolveSourceMapLocations": [ 14 | "${workspaceFolder}/**", 15 | "!**/node_modules/**" 16 | ], 17 | "skipFiles": ["/**", "node_modules/**"] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Endel Dreyer 2 | 3 | MIT License: 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | On this repository you can find basic usage examples of how to use Colyseus along with the JavaScript client. 4 | 5 | **Live demo** 6 | 7 | - [See live](https://examples.colyseus.dev/) 8 | - [See monitor panel](https://examples.colyseus.dev/) 9 | 10 | ## How to run 11 | 12 | ``` 13 | git clone https://github.com/colyseus/colyseus-examples.git 14 | cd colyseus-examples 15 | npm install 16 | npm start 17 | ``` 18 | 19 | Open [http://localhost:2567](http://localhost:2567) in your browser. 20 | 21 | ## License 22 | 23 | MIT 24 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Colyseus Multiplayer", 3 | "description": "Multiplayer Game Server for Node.js", 4 | "repository": "https://github.com/colyseus/colyseus", 5 | "logo": "https://github.com/colyseus/colyseus/blob/master/media/logo.png?raw=true", 6 | "keywords": ["node", "multiplayer", "colyseus"], 7 | "env": { 8 | "NPM_CONFIG_PRODUCTION": { 9 | "description": "Leave as 'false' to install required devDependencies.", 10 | "value": "false" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | 3 | /** 4 | * COLYSEUS CLOUD WARNING: 5 | * ---------------------- 6 | * PLEASE DO NOT UPDATE THIS FILE MANUALLY AS IT MAY CAUSE DEPLOYMENT ISSUES 7 | */ 8 | 9 | module.exports = { 10 | apps : [{ 11 | name: "colyseus-app", 12 | script: 'build/index.js', 13 | time: true, 14 | watch: false, 15 | instances: os.cpus().length, 16 | exec_mode: 'fork', 17 | wait_ready: true, 18 | env_production: { 19 | NODE_ENV: 'production' 20 | } 21 | }], 22 | }; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "colyseus-examples", 3 | "version": "0.16.0", 4 | "description": "Basic examples demonstrating Colyseus features", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "start": "tsx watch src/index.ts", 8 | "build": "tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "engines": { 12 | "node": ">=16.x" 13 | }, 14 | "author": "Endel Dreyer", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@colyseus/uwebsockets-transport": "^0.16.0", 18 | "@types/cors": "^2.8.6", 19 | "@types/express": "^4.17.14", 20 | "tsx": "^3.12.7", 21 | "typescript": "^4.8.4" 22 | }, 23 | "dependencies": { 24 | "@colyseus/auth": "^0.16.0", 25 | "@colyseus/core": "^0.16.0", 26 | "@colyseus/monitor": "^0.16.0", 27 | "@colyseus/playground": "^0.16.0", 28 | "@colyseus/tools": "^0.16.0", 29 | "colyseus": "^0.16.0", 30 | "cors": "^2.8.5", 31 | "express": "^4.16.4", 32 | "serve-index": "^1.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app.config.ts: -------------------------------------------------------------------------------- 1 | import config from "@colyseus/tools"; 2 | import { monitor } from "@colyseus/monitor"; 3 | import { playground } from "@colyseus/playground"; 4 | import { auth } from "@colyseus/auth"; 5 | import path from 'path'; 6 | import serveIndex from 'serve-index'; 7 | import express from 'express'; 8 | 9 | // import { uWebSocketsTransport} from "@colyseus/uwebsockets-transport"; 10 | import "./config/auth"; 11 | 12 | // Import demo room handlers 13 | import { LobbyRoom, RelayRoom } from 'colyseus'; 14 | import { ChatRoom } from "./rooms/01-chat-room"; 15 | import { StateHandlerRoom } from "./rooms/02-state-handler"; 16 | import { AuthRoom } from "./rooms/03-auth"; 17 | import { ReconnectionRoom } from './rooms/04-reconnection'; 18 | import { CustomLobbyRoom } from './rooms/07-custom-lobby-room'; 19 | 20 | export default config({ 21 | options: { 22 | devMode: true, 23 | }, 24 | 25 | initializeGameServer: (gameServer) => { 26 | // Define "lobby" room 27 | gameServer.define("lobby", LobbyRoom); 28 | 29 | // Define "relay" room 30 | gameServer.define("relay", RelayRoom, { maxClients: 4 }) 31 | .enableRealtimeListing(); 32 | 33 | // Define "chat" room 34 | gameServer.define("chat", ChatRoom) 35 | .enableRealtimeListing(); 36 | 37 | // Register ChatRoom with initial options, as "chat_with_options" 38 | // onInit(options) will receive client join options + options registered here. 39 | gameServer.define("chat_with_options", ChatRoom, { 40 | custom_options: "you can use me on Room#onCreate" 41 | }); 42 | 43 | // Define "state_handler" room 44 | gameServer.define("state_handler", StateHandlerRoom) 45 | .enableRealtimeListing(); 46 | 47 | // Define "auth" room 48 | gameServer.define("auth", AuthRoom) 49 | .enableRealtimeListing(); 50 | 51 | // Define "reconnection" room 52 | gameServer.define("reconnection", ReconnectionRoom) 53 | .enableRealtimeListing(); 54 | 55 | // Define "custom_lobby" room 56 | gameServer.define("custom_lobby", CustomLobbyRoom); 57 | 58 | gameServer.onShutdown(function(){ 59 | console.log(`game server is going down.`); 60 | }); 61 | 62 | 63 | }, 64 | 65 | initializeExpress: (app) => { 66 | // (optional) auth module 67 | app.use(auth.prefix, auth.routes()); 68 | 69 | // (optional) client playground 70 | app.use('/playground', playground); 71 | 72 | // (optional) web monitoring panel 73 | app.use('/colyseus', monitor()); 74 | 75 | app.use('/', serveIndex(path.join(__dirname, "static"), {'icons': true})) 76 | app.use('/', express.static(path.join(__dirname, "static"))); 77 | }, 78 | 79 | 80 | beforeListen: () => { 81 | /** 82 | * Before before gameServer.listen() is called. 83 | */ 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /src/config/auth.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@colyseus/auth"; 2 | 3 | const fakeDb: any[] = []; 4 | 5 | auth.settings.onFindUserByEmail = async (email) => { 6 | const userFound = fakeDb.find((user) => user.email === email);; 7 | 8 | // return a copy of the user object 9 | return userFound && JSON.parse(JSON.stringify(userFound)); 10 | }; 11 | 12 | auth.settings.onRegisterWithEmailAndPassword = async (email, password) => { 13 | const user = { email, password, name: email.split("@")[0], errorServerIsStringButClientIsInt: "this should not crash the client", someAdditionalData: true, }; 14 | 15 | // keep a copy of the user object 16 | fakeDb.push(JSON.parse(JSON.stringify(user))); 17 | 18 | return user; 19 | }; 20 | 21 | auth.settings.onRegisterAnonymously = async (options) => { 22 | return { 23 | anonymousId: Math.round(Math.random() * 1000), 24 | anonymous: true, 25 | ...options 26 | }; 27 | }; 28 | 29 | export default auth; 30 | 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * IMPORTANT: 3 | * --------- 4 | * Do not manually edit this file if you'd like to host your server on Colyseus Cloud 5 | * 6 | * If you're self-hosting (without Colyseus Cloud), you can manually 7 | * instantiate a Colyseus Server as documented here: 8 | * 9 | * See: https://docs.colyseus.io/server/api/#constructor-options 10 | */ 11 | import { listen } from "@colyseus/tools"; 12 | 13 | // Import arena config 14 | import appConfig from "./app.config"; 15 | 16 | // Create and listen on 2567 (or PORT environment variable.) 17 | listen(appConfig); 18 | -------------------------------------------------------------------------------- /src/rooms/01-chat-room.ts: -------------------------------------------------------------------------------- 1 | import { Room } from "colyseus"; 2 | 3 | export class ChatRoom extends Room { 4 | // this room supports only 4 clients connected 5 | maxClients = 4; 6 | 7 | onCreate (options) { 8 | console.log("ChatRoom created!", options); 9 | 10 | this.onMessage("message", (client, message) => { 11 | console.log("ChatRoom received message from", client.sessionId, ":", message); 12 | this.broadcast("messages", `(${client.sessionId}) ${message}`); 13 | }); 14 | } 15 | 16 | onJoin (client) { 17 | this.broadcast("messages", `${ client.sessionId } joined.`); 18 | } 19 | 20 | onLeave (client) { 21 | this.broadcast("messages", `${ client.sessionId } left.`); 22 | } 23 | 24 | onDispose () { 25 | console.log("Dispose ChatRoom"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/rooms/02-state-handler.ts: -------------------------------------------------------------------------------- 1 | import { Room, Client } from "colyseus"; 2 | import { Schema, type, MapSchema } from "@colyseus/schema"; 3 | 4 | export class Player extends Schema { 5 | @type("number") 6 | x = Math.floor(Math.random() * 400); 7 | 8 | @type("number") 9 | y = Math.floor(Math.random() * 400); 10 | } 11 | 12 | export class State extends Schema { 13 | @type({ map: Player }) 14 | players = new MapSchema(); 15 | 16 | something = "This attribute won't be sent to the client-side"; 17 | 18 | createPlayer(sessionId: string) { 19 | this.players.set(sessionId, new Player()); 20 | } 21 | 22 | removePlayer(sessionId: string) { 23 | this.players.delete(sessionId); 24 | } 25 | 26 | movePlayer (sessionId: string, movement: any) { 27 | if (movement.x) { 28 | this.players.get(sessionId).x += movement.x * 10; 29 | 30 | } else if (movement.y) { 31 | this.players.get(sessionId).y += movement.y * 10; 32 | } 33 | } 34 | } 35 | 36 | export class StateHandlerRoom extends Room { 37 | maxClients = 4; 38 | state = new State(); 39 | 40 | onCreate (options) { 41 | console.log("StateHandlerRoom created!", options); 42 | 43 | this.onMessage("move", (client, data) => { 44 | console.log("StateHandlerRoom received message from", client.sessionId, ":", data); 45 | this.state.movePlayer(client.sessionId, data); 46 | }); 47 | } 48 | 49 | // onAuth(client, options, req) { 50 | // return true; 51 | // } 52 | 53 | onJoin (client: Client) { 54 | // client.send("hello", "world"); 55 | console.log(client.sessionId, "joined!"); 56 | this.state.createPlayer(client.sessionId); 57 | } 58 | 59 | onLeave (client) { 60 | console.log(client.sessionId, "left!"); 61 | this.state.removePlayer(client.sessionId); 62 | } 63 | 64 | onDispose () { 65 | console.log("Dispose StateHandlerRoom"); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/rooms/03-auth.ts: -------------------------------------------------------------------------------- 1 | import { Room, Client } from "colyseus"; 2 | 3 | const FACEBOOK_APP_TOKEN = "135829507120512|3a97320bee18f2286d6243dcf4cc7a23"; 4 | 5 | export class AuthRoom extends Room { 6 | onCreate (options: any) { 7 | console.log("StateHandlerRoom created!", options); 8 | 9 | this.onMessage("*", (client, type, message) => { 10 | console.log("AuthRoom received message from", client.sessionId, ":", message); 11 | }); 12 | } 13 | 14 | async onAuth (client: Client, options: any) { 15 | const response = await fetch(`https://graph.facebook.com/debug_token?input_token=${options.accessToken}&access_token=${FACEBOOK_APP_TOKEN}`, { 16 | headers: { 17 | 'Accept': 'application/json' 18 | } 19 | }).then(res => res.json()); 20 | 21 | return (response as any).data; 22 | } 23 | 24 | onJoin (client: Client, options: any, auth: any) { 25 | console.log(client.sessionId, "joined successfully"); 26 | console.log("Auth data: ", auth); 27 | } 28 | 29 | onLeave (client: Client) { 30 | console.log(client.sessionId, "left"); 31 | } 32 | 33 | onDispose () { 34 | console.log("Dispose AuthRoom"); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/rooms/04-reconnection.ts: -------------------------------------------------------------------------------- 1 | import { Room, Client } from "colyseus"; 2 | 3 | export class ReconnectionRoom extends Room { 4 | onCreate (options: any) { 5 | } 6 | 7 | onJoin (client: Client, options: any, auth: any) { 8 | client.send("status", "Welcome!"); 9 | } 10 | 11 | async onLeave (client: Client, consented?: boolean) { 12 | console.log(client.sessionId, "left", { consented }); 13 | 14 | try { 15 | if (consented) { 16 | /* 17 | * Optional: 18 | * you may want to allow reconnection if the client manually closed the connection. 19 | */ 20 | throw new Error("left_manually"); 21 | } 22 | 23 | await this.allowReconnection(client, 10); 24 | console.log("Reconnected!"); 25 | 26 | client.send("status", "Welcome back!"); 27 | 28 | } catch (e) { 29 | console.log(e); 30 | 31 | } 32 | } 33 | 34 | onDispose () { 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/rooms/07-custom-lobby-room.ts: -------------------------------------------------------------------------------- 1 | import { Schema, type } from "@colyseus/schema"; 2 | import { Client, LobbyRoom } from "colyseus"; 3 | 4 | class LobbyState extends Schema { 5 | @type("string") custom: string ; 6 | } 7 | 8 | export class CustomLobbyRoom extends LobbyRoom { 9 | state = new LobbyState(); 10 | 11 | async onCreate(options) { 12 | await super.onCreate(options); 13 | 14 | } 15 | 16 | onJoin(client: Client, options) { 17 | super.onJoin(client, options); 18 | this.state.custom = client.sessionId; 19 | } 20 | 21 | onLeave(client) { 22 | super.onLeave(client); 23 | } 24 | } -------------------------------------------------------------------------------- /src/static/01-chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | colyseus 17 |

18 | 19 |

This room doesn't use the room's state. It just broadcast messages through "broadcast" method.

20 | 21 | Messages
22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/static/02-state-handler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 27 |

28 | colyseus 29 |

30 | 31 |

This example shows how to use custom data structures in your room's state.

32 | 33 | commands
34 | 35 | 36 | 37 |
38 | 39 | 40 | 41 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/static/03-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | colyseus 17 |

18 | 19 |

This example shows how to authenticate and retrieve user data before the websocket handshake.

20 | 21 |

Open Developer Tools for log messages.

22 | 23 |

Commands

24 | 25 | 26 | 27 | 28 |

We do not store any data from your Facebook account during this process. Facebook is only used as an example of authentication provider.

29 | 30 | 41 | 42 | 81 | 82 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/static/04-reconnection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | colyseus 17 |

18 | 19 |

This example shows how to use

20 | 24 |

...to reestablish a connection into a Room.

25 | 26 |

Open Developer Tools for log messages.

27 | 28 |

Commands

29 | 30 | 31 | 32 | 33 | 34 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/static/05-lobby-room.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | colyseus 17 |

18 | 19 |

This example shows how to use LobbyRoom:

20 | 26 | 27 |

Open Developer Tools for log messages.

28 | 29 |

Commands

30 | 31 | 32 | 33 | 34 |

All rooms:

35 | 36 | 37 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/static/06-relay-room.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 27 |

28 | colyseus 29 |

30 | 31 |

This example shows how to use RelayRoom:

32 | 37 | 38 | commands
39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 47 |
48 | 49 | 50 | 51 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/static/07-custom-lobby-room.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

16 | colyseus 17 |

18 | 19 |

This example shows how to use a custom LobbyRoom:

20 | 24 | 25 |

Open Developer Tools for log messages.

26 | 27 |

Commands

28 | 29 | 30 | 31 | 32 |

All rooms:

33 | 34 | 35 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build", 4 | "module": "commonjs", 5 | "lib": ["es6"], 6 | "target": "es2016", 7 | "declaration": true, 8 | "noImplicitAny": false, 9 | "experimentalDecorators": true, 10 | "sourceMap": false, 11 | "esModuleInterop": true, 12 | "strict": true, 13 | "allowJs": true, 14 | "strictNullChecks": false, 15 | "skipLibCheck": true, 16 | "forceConsistentCasingInFileNames": true 17 | }, 18 | "include": [ 19 | "src" 20 | ] 21 | } 22 | --------------------------------------------------------------------------------