伺服器 API » 房間¶
Room 類的作用是實現遊戲會話, 並且/或作為一組客戶端之間的通信通道.
- 預設情況下,在匹配期間 on demand 創建房間
- 必須使用
.define()發布 Room 類
import http from "http";
import { Room, Client } from "colyseus";
export class MyRoom extends Room {
// When room is initialized
onCreate (options: any) { }
// Authorize client based on provided options before WebSocket handshake is complete
onAuth (client: Client, options: any, request: http.IncomingMessage) { }
// When client successfully join the room
onJoin (client: Client, options: any, auth: any) { }
// When a client leaves the room
onLeave (client: Client, consented: boolean) { }
// Cleanup callback, called after there are no more clients in the room. (see `autoDispose`)
onDispose () { }
}
const colyseus = require('colyseus');
export class MyRoom extends colyseus.Room {
// When room is initialized
onCreate (options) { }
// Authorize client based on provided options before WebSocket handshake is complete
onAuth (client, options, request) { }
// When client successfully join the room
onJoin (client, options, auth) { }
// When a client leaves the room
onLeave (client, consented) { }
// Cleanup callback, called after there are no more clients in the room. (see `autoDispose`)
onDispose () { }
}
房間生命周期事件¶
- 自動調用房間生命周期事件.
- 每個生命周期事件都支持
async/await選項.
onCreate (options)¶
在匹配器創建房間之後, 進行一次調用.
The options argument is provided by the client upon room creation:
// Client-side - JavaScript SDK
client.joinOrCreate("my_room", {
name: "Jake",
map: "de_dust2"
})
// onCreate() - options are:
// {
// name: "Jake",
// map: "de_dust2"
// }
The server may overwrite options during .define() for authortity:
// Server-side
gameServer.define("my_room", MyRoom, {
map: "cs_assault"
})
// onCreate() - options are:
// {
// name: "Jake",
// map: "cs_assault"
// }
在本例中, 在 onCreate() 期間, map 選項是 "cs_assault" ,在 onJoin() 期間的選項是 "de_dust2".
onAuth (client, options, request)¶
在 onJoin() 之前, 將執行 onAuth() 方法. 在客戶進入房間時, 可以使用此方法驗證身份.
- 如果
onAuth()返回一個 truthy 值, 將調用onJoin(), 並將返回值作為第三個參數. - 如果
onAuth()返回 falsy 值, 將立即拒絕客戶, 導致在客戶端調用匹配函數失敗. - 也可以拋出一個
ServerError, 以便在客戶端處理自定義錯誤.
如果此方法未被實現, 將始終返回 true, 從而允許任何客戶連線.
正在獲取玩家的 IP 地址
可以使用 request 變數檢索用戶的 IP 地址, http 標頭和更多資訊. 例如: request.headers['x-forwarded-for'] || request.connection.remoteAddress
Implementations examples
import { Room, ServerError } from "colyseus";
class MyRoom extends Room {
async onAuth (client, options, request) {
/**
* Alternatively, you can use `async` / `await`,
* which will return a `Promise` under the hood.
*/
const userData = await validateToken(options.accessToken);
if (userData) {
return userData;
} else {
throw new ServerError(400, "bad access token");
}
}
}
import { Room } from "colyseus";
class MyRoom extends Room {
onAuth (client, options, request): boolean {
/**
* You can immediatelly return a `boolean` value.
*/
if (options.password === "secret") {
return true;
} else {
throw new ServerError(400, "bad access token");
}
}
}
import { Room } from "colyseus";
class MyRoom extends Room {
onAuth (client, options, request): Promise<any> {
/**
* You can return a `Promise`, and perform some asynchronous task to validate the client.
*/
return new Promise((resolve, reject) => {
validateToken(options.accessToken, (err, userData) => {
if (!err) {
resolve(userData);
} else {
reject(new ServerError(400, "bad access token"));
}
});
});
}
}
Client-side examples
在客戶端, 可以使用來自於您選擇的身份驗證服務(例如Facebook)的令牌調用匹配方法(join, joinOrCreate 等):
client.joinOrCreate("world", {
accessToken: yourFacebookAccessToken
}).then((room) => {
// success
}).catch((err) => {
// handle error...
err.code // 400
err.message // "bad access token"
});
try {
var room = await client.JoinOrCreate<YourStateClass>("world", new {
accessToken = yourFacebookAccessToken
});
// success
} catch (err) {
// handle error...
err.code // 400
err.message // "bad access token"
}
client:join_or_create("world", {
accessToken = yourFacebookAccessToken
}, function(err, room)
if err then
-- handle error...
err.code -- 400
err.message -- "bad access token"
return
end
-- success
end)
client.joinOrCreate("world", {
accessToken: yourFacebookAccessToken
}, YourStateClass, function (err, room) {
if (err != null) {
// handle error...
err.code // 400
err.message // "bad access token"
return;
}
// success
})
client.joinOrCreate("world", {
{ "accessToken", yourFacebookAccessToken }
}, [=](MatchMakeError *err, Room<YourStateClass>* room) {
if (err != "") {
// handle error...
err.code // 400
err.message // "bad access token"
return;
}
// success
});
onJoin (client, options, auth?)¶
Parameters:
客戶端客戶端實例.options: 在 Server#define() 中指定的合並值, 帶有客戶client.join()時提供的選項auth: (可選) 返回的身份驗證方法數據onAuth
在 requestJoin 和 onAuth 完成後, 客戶成功進入房間時調用.
onLeave (client, consented)¶
當客戶離開房間時調用. 如果由 initiated by the client 發起斷開, consented 參數將是 true, 否則將是 false.
可以將此函數定義為 async. 參見 graceful shutdown.
onLeave(client, consented) {
if (this.state.players.has(client.sessionId)) {
this.state.players.delete(client.sessionId);
}
}
async onLeave(client, consented) {
const player = this.state.players.get(client.sessionId);
await persistUserOnDatabase(player);
}
onDispose ()¶
在銷毀房間之前調用 onDispose() 方法, 在發生以下情況時調用:
- 房間裏沒有客戶, 而且
autoDispose被設置為true(預設值) - 可以手動調用
.disconnect().
可以將 async onDispose() 定義為異步方法, 以便在數據庫中保留一些數據. 事實上, 在遊戲結束後, 很適合使用此方法在數據庫中保留玩家的數據.
示例房間¶
此示例演示實現 onCreate, onJoin 和 onMessage 方法的完整房間.
import { Room, Client } from "colyseus";
import { Schema, MapSchema, type } from "@colyseus/schema";
// An abstract player object, demonstrating a potential 2D world position
export class Player extends Schema {
@type("number")
x: number = 0.11;
@type("number")
y: number = 2.22;
}
// Our custom game state, an ArraySchema of type Player only at the moment
export class State extends Schema {
@type({ map: Player })
players = new MapSchema<Player>();
}
export class GameRoom extends Room<State> {
// Colyseus will invoke when creating the room instance
onCreate(options: any) {
// initialize empty room state
this.setState(new State());
// Called every time this room receives a "move" message
this.onMessage("move", (client, data) => {
const player = this.state.players.get(client.sessionId);
player.x += data.x;
player.y += data.y;
console.log(client.sessionId + " at, x: " + player.x, "y: " + player.y);
});
}
// Called every time a client joins
onJoin(client: Client, options: any) {
this.state.players.set(client.sessionId, new Player());
}
}
const colyseus = require('colyseus');
const schema = require('@colyseus/schema');
// An abstract player object, demonstrating a potential 2D world position
exports.Player = class Player extends schema.Schema {
constructor() {
super();
this.x = 0.11;
this.y = 2.22;
}
}
schema.defineTypes(Player, {
x: "number",
y: "number",
});
// Our custom game state, an ArraySchema of type Player only at the moment
exports.State = class State extends schema.Schema {
constructor() {
super();
this.players = new schema.MapSchema();
}
}
defineTypes(State, {
players: { map: Player }
});
exports.GameRoom = class GameRoom extends colyseus.Room {
// Colyseus will invoke when creating the room instance
onCreate(options) {
// initialize empty room state
this.setState(new State());
// Called every time this room receives a "move" message
this.onMessage("move", (client, data) => {
const player = this.state.players.get(client.sessionId);
player.x += data.x;
player.y += data.y;
console.log(client.sessionId + " at, x: " + player.x, "y: " + player.y);
});
}
// Called every time a client joins
onJoin(client, options) {
this.state.players.set(client.sessionId, new Player());
}
}
公用方法¶
房間句柄提供這些方法.
onMessage (type, callback)¶
註冊一個回呼, 以處理客戶端發送的某種類型的資訊.
type 參數可以是 string 或 number
Callback for specific type of message
onCreate () {
this.onMessage("action", (client, message) => {
console.log(client.sessionId, "sent 'action' message: ", message);
});
}
Callback for ALL messages
可以註冊單個回呼, 以處理所有其它類型的消息.
onCreate () {
this.onMessage("action", (client, message) => {
//
// Triggers when 'action' message is sent.
//
});
this.onMessage("*", (client, type, message) => {
//
// Triggers when any other type of message is sent,
// excluding "action", which has its own specific handler defined above.
//
console.log(client.sessionId, "sent", type, message);
});
}
Use room.send() from the client-side SDK to send messages
Check out room.send()} section.
setState (object)¶
設置同步房間狀態. 參見 State Synchronization 和 Schema 了解更多資訊.
Tip
通常,可以在 onCreate() 期間調用此方法一次
Warning
不要調用 .setState() 來進行每次房間更新. 每次調用時, 將會重置二叉樹路徑算法.
setSimulationInterval (callback[, milliseconds=16.6])¶
(可選)設置一個可以更改遊戲狀態的模擬間隔期. 此模擬間隔期間是您的遊戲循環周期. 預設模擬間隔期: 16.6ms (60fps)
onCreate () {
this.setSimulationInterval((deltaTime) => this.update(deltaTime));
}
update (deltaTime) {
// implement your physics or world updates here!
// this is a good place to update the room state
}
setPatchRate (milliseconds)¶
設置將補丁狀態發送至所有客戶端的頻率. 預設值為 50ms (20fps)
setPrivate (bool)¶
將房間列表設置為私有(或轉換為公有, 如果提供 false).
在 >getAvailableRooms() 方法中未列出私有房間.
setMetadata (metadata)¶
為此房間設置元數據. 每個房間實例都可能附加了元數據 - 附加元數據的唯一目的是在從客戶端獲取可用房間列表時, 將一個房間與另一個房間區分開來, 通過 roomId 連線到房間, 並使用 client.getAvailableRooms().
// server-side
this.setMetadata({ friendlyFire: true });
現在, 房間已經有附加的元數據, 舉例來說, 客戶端可以檢查哪個房間有 friendlyFire, 並且可以通過其 roomId 直接連線到房間:
// client-side
client.getAvailableRooms("battle").then(rooms => {
for (var i=0; i<rooms.length; i++) {
if (room.metadata?.friendlyFire) {
//
// join the room with `friendlyFire` by id:
//
var room = client.join(room.roomId);
return;
}
}
});
setSeatReservationTime (seconds)¶
設置房間可以等待客戶端有效加入的秒數 .應該考慮 onAuth() 需要等待多長時間, 以設置不同的座位預訂時間. 預設值為 15 秒.
如果想要全局更改座位預訂時間, 可以設置 COLYSEUS_SEAT_RESERVATION_TIME 環境變數.
send (client, message)¶
已棄用
this.send() 已被棄用. 請使用 client.send() instead.
broadcast (type, message, options?)¶
向所有連線的客戶端發送一條消息.
可用的選項為:
except: aClient不會發送消息至afterNextPatch: 等待, 直到下一補丁廣播消息
廣播示例¶
向所有客戶端廣播一條消息:
onCreate() {
this.onMessage("action", (client, message) => {
// broadcast a message to all clients
this.broadcast("action-taken", "an action has been taken!");
});
}
向所有客戶端廣播一條消息, 發送者除外:
onCreate() {
this.onMessage("fire", (client, message) => {
// sends "fire" event to every client, except the one who triggered it.
this.broadcast("fire", message, { except: client });
});
}
僅在應用狀態變更之後, 向所有客戶端廣播一條消息:
onCreate() {
this.onMessage("destroy", (client, message) => {
// perform changes in your state!
this.state.destroySomething();
// this message will arrive only after new state has been applied
this.broadcast("destroy", "something has been destroyed", { afterNextPatch: true });
});
}
廣播一條架構編碼消息:
class MyMessage extends Schema {
@type("string") message: string;
}
// ...
onCreate() {
this.onMessage("action", (client, message) => {
const data = new MyMessage();
data.message = "an action has been taken!";
this.broadcast(data);
});
}
lock ()¶
鎖定房間將會從供新客戶連線的房間池中刪除房間.
unlock ()¶
解鎖房間會將房間返回至可供新客戶連線的房間池.
allowReconnection (client, seconds?)¶
允許指定的客戶 reconnect 房間. 必須在 onLeave() 方法中使用.
如果提供 seconds, 將在提供的秒數之後取消重新連線.
Return type:
allowReconnection()返回一個Deferred<Client>實例.Deferred是一個類似於 pormise 的類型Deferred類型可以通過調用.reject()強製拒絕 promise (參見第二個示例)
示例 在 20 秒超時後拒絕重新連線.
async onLeave (client: Client, consented: boolean) {
// flag client as inactive for other users
this.state.players.get(client.sessionId).connected = false;
try {
if (consented) {
throw new Error("consented leave");
}
// allow disconnected client to reconnect into this room until 20 seconds
await this.allowReconnection(client, 20);
// client returned! let's re-activate it.
this.state.players.get(client.sessionId).connected = true;
} catch (e) {
// 20 seconds expired. let's remove the client.
this.state.players.delete(client.sessionId);
}
}
示例 使用自定義邏輯拒絕重新連線.
async onLeave (client: Client, consented: boolean) {
// flag client as inactive for other users
this.state.players.get(client.sessionId).connected = false;
try {
if (consented) {
throw new Error("consented leave");
}
// get reconnection token
const reconnection = this.allowReconnection(client);
//
// here is the custom logic for rejecting the reconnection.
// for demonstration purposes of the API, an interval is created
// rejecting the reconnection if the player has missed 2 rounds,
// (assuming he's playing a turn-based game)
//
// in a real scenario, you would store the `reconnection` in
// your Player instance, for example, and perform this check during your
// game loop logic
//
const currentRound = this.state.currentRound;
const interval = setInterval(() => {
if ((this.state.currentRound - currentRound) > 2) {
// manually reject the client reconnection
reconnection.reject();
clearInterval(interval);
}
}, 1000);
// allow disconnected client to reconnect
await reconnection;
// client returned! let's re-activate it.
this.state.players.get(client.sessionId).connected = true;
} catch (e) {
// 20 seconds expired. let's remove the client.
this.state.players.delete(client.sessionId);
}
}
disconnect ()¶
斷開所有客戶斷, 然後銷毀房間.
broadcastPatch ()¶
您可能不需要這樣做!
框架會自動調用此方法.
此方法會檢查是否已經在 state 中發生變化(mutation), 並將變化廣播給所有已連線的客戶端.
如果想要控製何時廣播補丁, 可以禁用預設的補丁間隔時間來實現:
onCreate() {
// disable automatic patches
this.setPatchRate(null);
// ensure clock timers are enabled
this.setSimulationInterval(() => {/* */});
this.clock.setInterval(() => {
// only broadcast patches if your custom conditions are met.
if (yourCondition) {
this.broadcastPatch();
}
}, 2000);
}
公用屬性¶
roomId: string¶
一個唯一, 自動生成的 8 字符長度的房間 id.
在 onCreate() 期間, 可以更換 this.roomId.
使用自定義 roomId
查閱指南 Check out the guide How-to » Customize room id
roomName: string¶
房間名稱作為 gameServer.define() 的第一個參數.
狀態T¶
提供給 setState() 的狀態實例.
clients:客戶端¶
已連線的客戶端數組. 參見 Client instance.
maxClients: number¶
允許連線進入房間的最大客戶端數量. 當房間數量達到此限值時, 將自動鎖定. 除非通過 lock() 方法顯式鎖定, 否則, 將在客戶端自動斷開房間時立即解鎖房間.
patchRate: number¶
將房間狀態發送至客戶端的頻率, 單位為毫秒. 預設值為 50ms (20fps)
autoDispose: boolean¶
最近一次客戶斷開連線後, 自動銷毀房間. 預設值是 true
locked: boolean(只讀)¶
對於以下情況, 此屬性將發生改變:
時鐘ClockTimer¶
一個 ClockTimer 實例, 用於 timing events.
PresencePresence¶
presence 實例. 查閱 Presence API 了解更多詳細資訊.
客戶端¶
伺服器端的 client 實例負責伺服器與客戶端之間的 transport 層. 不應該與 Client from the client-side SDK 混淆, 因為它們具有完全不同的目的!
可以通過 this.clients, Room#onJoin(), Room#onLeave() 和 Room#onMessage() 操作 client 實例.
Note
這是來自於 ws 包的原始 WebSocket 連線. 還有更多的方法可用, 但是不建議用於 Colyseus.
屬性¶
sessionId: string¶
每個會話的唯一 id.
Note
在客戶端, 可以在 room 實例中找到 sessionId.
userData: any¶
可用於存儲關於客戶端連線的自定義數據. userData 並不同步於 not 客戶端, 僅用於保留與其連線相關的用戶數據.
onJoin(client, options) {
client.userData = { playerNumber: this.clients.length };
}
onLeave(client) {
console.log(client.userData.playerNumber);
}
auth: any¶
onAuth() 期間返回的自定義數據.
方法.¶
send(type, message)¶
發送消息類型至客戶端. 消息使用 MsgPack 編碼, 僅含有可序列化 JSON 數據結構.
type 可以是 string 或 number.
Sending a message:
//
// sending message with a string type ("powerup")
//
client.send("powerup", { kind: "ammo" });
//
// sending message with a number type (1)
//
client.send(1, { kind: "ammo"});
Tip
leave(code?: number)¶
客戶端 與房間強製斷開連線. 您可以在關閉連線時發送一個數值介於 4000 和 4999 之間的自定義 code (見 WebSocket 關閉代碼表)
Tip
這將在客戶端觸發 room.onLeave 事件.
WebSocket 關閉代碼表¶
| Close code (uint16) | Codename | Internal | Customizable | Description |
|---|---|---|---|---|
0 - 999 |
Yes | No | Unused | |
1000 |
CLOSE_NORMAL |
No | No | Successful operation / regular socket shutdown |
1001 |
CLOSE_GOING_AWAY |
No | No | Client is leaving (browser tab closing) |
1002 |
CLOSE_PROTOCOL_ERROR |
Yes | No | Endpoint received a malformed frame |
1003 |
CLOSE_UNSUPPORTED |
Yes | No | Endpoint received an unsupported frame (e.g. binary-only endpoint received text frame) |
1004 |
Yes | No | Reserved | |
1005 |
CLOSED_NO_STATUS |
Yes | No | Expected close status, received none |
1006 |
CLOSE_ABNORMAL |
Yes | No | No close code frame has been receieved |
1007 |
Unsupported payload | Yes | No | Endpoint received inconsistent message (e.g. malformed UTF-8) |
1008 |
Policy violation | No | No | Generic code used for situations other than 1003 and 1009 |
1009 |
CLOSE_TOO_LARGE |
No | No | Endpoint won't process large frame |
1010 |
Mandatory extension | No | No | Client wanted an extension which server did not negotiate |
1011 |
Server error | No | No | Internal server error while operating |
1012 |
Service restart | No | No | Server/service is restarting |
1013 |
Try again later | No | No | Temporary server condition forced blocking client's request |
1014 |
Bad gateway | No | No | Server acting as gateway received an invalid response |
1015 |
TLS handshake fail | Yes | No | Transport Layer Security handshake failure |
1016 - 1999 |
Yes | No | Reserved for future use by the WebSocket standard. | |
2000 - 2999 |
Yes | Yes | Reserved for use by WebSocket extensions | |
3000 - 3999 |
No | Yes | Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve. | |
4000 - 4999 |
No | Yes | Available for applications |
error(code, message)¶
將錯誤及代碼與消息一並發送給客戶端. 客戶端可以在 onError 對其進行處理