快速上手多人游戲服務器開發。后續會基於 Google Agones
,更新相關 K8S
運維、大規模快速擴展專用游戲服務器的文章。擁抱☁️原生🤗 Cloud-Native!
系列
ColyseusJS 輕量級多人游戲服務器開發框架 - 中文手冊(上)
Web-Socket Server
Server
Server
負責提供 WebSocket server 來實現服務器和客戶端之間的通信。
constructor (options)
options.server
要綁定 WebSocket Server 的 HTTP server。你也可以在你的服務器上使用 express
。
// Colyseus + Express
import { Server } from "colyseus";
import { createServer } from "http";
import express from "express";
const port = Number(process.env.port) || 3000;
const app = express();
app.use(express.json());
const gameServer = new Server({
server: createServer(app)
});
gameServer.listen(port);
// Colyseus (barebones)
import { Server } from "colyseus";
const port = process.env.port || 3000;
const gameServer = new Server();
gameServer.listen(port);
options.pingInterval
服務器 "ping"
客戶端的毫秒數。默認值: 3000
如果客戶端在 pingMaxRetries
重試后不能響應,則將強制斷開連接。
options.pingMaxRetries
沒有響應的最大允許 ping
數。默認值: 2
。
options.verifyClient
這個方法發生在 WebSocket
握手之前。如果 verifyClient
沒有設置,那么握手會被自動接受。
-
info
(Object)origin
(String) 客戶端指定的Origin header
中的值。req
(http.IncomingMessage) 客戶端HTTP GET
請求。secure
(Boolean)true
,如果req.connection.authorized
或req.connection.encrypted
被設置。
-
next
(Function) 用戶必須在檢查info
字段后調用該回調。此回調中的參數為:result
(Boolean) 是否接受握手。code
(Number) 當result
為false
時,該字段決定發送給客戶端的 HTTP 錯誤狀態碼。name
(String) 當result
為false
時,該字段決定 HTTP 原因短語。
import { Server } from "colyseus";
const gameServer = new Server({
// ...
verifyClient: function (info, next) {
// validate 'info'
//
// - next(false) will reject the websocket handshake
// - next(true) will accept the websocket handshake
}
});
options.presence
當通過多個進程/機器擴展 Colyseus
時,您需要提供一個狀態服務器。
import { Server, RedisPresence } from "colyseus";
const gameServer = new Server({
// ...
presence: new RedisPresence()
});
當前可用的狀態服務器是:
RedisPresence
(在單個服務器和多個服務器上擴展)
options.gracefullyShutdown
自動注冊 shutdown routine
。默認為 true
。如果禁用,則應在關閉進程中手動調用 gracefullyShutdown()
方法。
define (name: string, handler: Room, options?: any)
定義一個新的 room handler
。
Parameters:
name: string
-room
的公共名稱。當從客戶端加入room
時,您將使用這個名稱。handler: Room
- 引用Room
handler 類。options?: any
-room
初始化的自定義選項。
// Define "chat" room
gameServer.define("chat", ChatRoom);
// Define "battle" room
gameServer.define("battle", BattleRoom);
// Define "battle" room with custom options
gameServer.define("battle_woods", BattleRoom, { map: "woods" });
"多次定義同一個 room handler":
- 您可以使用不同的
options
多次定義同一個room handler
。當調用Room#onCreate()
時,options
將包含您在Server#define()
中指定的合並值 + 創建房間時提供的選項。
Matchmaking 過濾器: filterBy(options)
參數
options: string[]
- 選項名稱的列表
當一個房間由 create()
或 joinOrCreate()
方法創建時,只有 filterBy()
方法定義的 options
將被存儲在內部,並用於在 join()
或 joinOrCreate()
調用中過濾出相關 rooms
。
示例: 允許不同的“游戲模式”。
gameServer
.define("battle", BattleRoom)
.filterBy(['mode']);
無論何時創建房間,mode
選項都將在內部存儲。
client.joinOrCreate("battle", { mode: "duo" }).then(room => {/* ... */});
您可以在 onCreate()
和/或 onJoin()
中處理提供的選項,以在 room
實現中實現請求的功能。
class BattleRoom extends Room {
onCreate(options) {
if (options.mode === "duo") {
// do something!
}
}
onJoin(client, options) {
if (options.mode === "duo") {
// put this player into a team!
}
}
}
示例: 通過內置的 maxClients
進行過濾
maxClients
是一個用於 matchmaking
的內部變量,也可以用於過濾。
gameServer
.define("battle", BattleRoom)
.filterBy(['maxClients']);
然后客戶端可以要求加入一個能夠容納一定數量玩家的房間。
client.joinOrCreate("battle", { maxClients: 10 }).then(room => {/* ... */});
client.joinOrCreate("battle", { maxClients: 20 }).then(room => {/* ... */});
Matchmaking 優先級: sortBy(options)
您還可以根據創建時加入房間的信息為加入房間賦予不同的優先級。
options
參數是一個鍵值對象,在左側包含字段名稱,在右側包含排序方向。排序方向可以是以下值之一:-1
, "desc"
,"descending"
,1
,"asc"
或 "ascending"
。
示例: 按內置的 clients
排序
clients
是為 matchmaking
而存儲的內部變量,其中包含當前已連接客戶端的數量。在以下示例中,連接最多客戶端的房間將具有優先權。使用 -1
,"desc"
或 "descending"
降序排列:
gameServer
.define("battle", BattleRoom)
.sortBy({ clients: -1 });
要按最少數量的玩家進行排序,您可以做相反的事情。將 1
,"asc"
或 "ascending"
用於升序:
gameServer
.define("battle", BattleRoom)
.sortBy({ clients: 1 });
啟用大廳的實時 room 列表
為了允許 LobbyRoom
接收來自特定房間類型的更新,您應該在啟用實時列表的情況下對其進行定義:
gameServer
.define("battle", BattleRoom)
.enableRealtimeListing();
監聽 room 實例事件
define
方法將返回已注冊的 handler
實例,您可以從 room
實例范圍之外監聽 match-making
事件。如:
"create"
- 當room
被創建時"dispose"
- 當room
被銷毀時"join"
- 當客戶端加入一個room
時"leave"
- 當客戶端離開一個room
時"lock"
- 當room
已經被鎖定時"unlock"
- 當room
已經被解鎖時
Usage:
gameServer
.define("chat", ChatRoom)
.on("create", (room) => console.log("room created:", room.roomId))
.on("dispose", (room) => console.log("room disposed:", room.roomId))
.on("join", (room, client) => console.log(client.id, "joined", room.roomId))
.on("leave", (room, client) => console.log(client.id, "left", room.roomId));
不鼓勵通過這些事件來操縱房間的 state
。而是在您的 room handler
中使用 abstract methods
simulateLatency (milliseconds: number)
這是一種便捷的方法,適用於您希望本地測試"laggy(滯后)"
客戶端的行為而不必將服務器部署到遠程雲的情況。
// Make sure to never call the `simulateLatency()` method in production.
if (process.env.NODE_ENV !== "production") {
// simulate 200ms latency between server and client.
gameServer.simulateLatency(200);
}
attach (options: any)
你通常不需要調用它。只有在你有非常明確的理由時才使用它。
連接或創建 WebSocket server。
options.server
:用於綁定 WebSocket 服務器的 HTTP 服務器。options.ws
:現有的可重用 WebSocket 服務器。
Express
import express from "express";
import { Server } from "colyseus";
const app = new express();
const gameServer = new Server();
gameServer.attach({ server: app });
http.createServer
import http from "http";
import { Server } from "colyseus";
const httpServer = http.createServer();
const gameServer = new Server();
gameServer.attach({ server: httpServer });
WebSocket.Server
import http from "http";
import express from "express";
import ws from "ws";
import { Server } from "colyseus";
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({
// your custom WebSocket.Server setup.
});
const gameServer = new Server();
gameServer.attach({ ws: wss });
listen (port: number)
將 WebSocket 服務器綁定到指定端口。
onShutdown (callback: Function)
注冊一個應該在進程關閉之前調用的回調。詳見 graceful shutdown
gracefullyShutdown (exit: boolean)
關閉所有房間並清理緩存數據。當清理完成時,返回一個 promise
。
除非 Server
構造函數中提供了 gracefullyShutdown: false
,否則該方法將被自動調用。
Room API (Server-side)
考慮到您已經設置了服務器,現在是時候注冊 room handlers
並開始接受用戶的連接了。
您將定義 room handlers
,以創建從 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 () { }
}
Room lifecycle
這些方法對應於房間(room
)的生命周期。
onCreate (options)
房間初始化后被調用一次。您可以在注冊房間處理程序時指定自定義初始化選項。
options
將包含您在 Server#define()
上指定的合並值 + client.joinOrCreate()
或 client.create()
所提供的選項。
onAuth (client, options, request)
onAuth()
方法將在 onJoin()
之前執行。它可以用來驗證加入房間的客戶端的真實性。
- 如果
onAuth()
返回一個真值,onJoin()
將被調用,並將返回值作為第三個參數。 - 如果
onAuth()
返回一個假值,客戶端將立即被拒絕,導致客戶端matchmaking
函數調用失敗。 - 您還可以拋出
ServerError
來公開要在客戶端處理的自定義錯誤。
如果不實現,它總是返回 true
- 允許任何客戶端連接。
"獲取玩家的 IP 地址":您可以使用 request
變量來檢索用戶的 IP 地址、http 頭等等。例如:request.headers['x-forwarded-for'] || request.connection.remoteAddress
實現示例
async / await
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");
}
}
}
Synchronous
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");
}
}
}
Promises
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"));
}
});
});
}
}
客戶端示例
在客戶端,您可以使用您選擇的某些身份驗證服務(例如 Facebook
)中的 token
,來調用 matchmaking
方法(join
,joinOrCreate
等):
client.joinOrCreate("world", {
accessToken: yourFacebookAccessToken
}).then((room) => {
// success
}).catch((err) => {
// handle error...
err.code // 400
err.message // "bad access token"
});
onJoin (client, options, auth?)
參數:
client
:客戶端
實例。options
: 合並在Server#define()
上指定的值和在client.join()
上提供的選項。auth
: (可選)auth
數據返回onAuth
方法。
當客戶端成功加入房間時,在 requestJoin
和 onAuth
成功后調用。
onLeave (client, consented)
當客戶端離開房間時被調用。如果斷開連接是由客戶端發起的,則 consented
參數將為 true
,否則為 false
。
你可以將這個函數定義為 async
。參閱 graceful shutdown
Synchronous
onLeave(client, consented) {
if (this.state.players.has(client.sessionId)) {
this.state.players.delete(client.sessionId);
}
}
Asynchronous
async onLeave(client, consented) {
const player = this.state.players.get(client.sessionId);
await persistUserOnDatabase(player);
}
onDispose ()
在房間被銷毀之前調用 onDispose()
方法,以下情況會發生:
- 房間里沒有更多的客戶端,並且
autoDispose
被設置為true
(默認) - 你手動調用
.disconnect()
您可以定義 async onDispose()
異步方法,以將一些數據持久化在數據庫中。實際上,這是在比賽結束后將玩家數據保留在數據庫中的好地方。
示例 room
這個例子演示了一個實現 onCreate
,onJoin
和 onMessage
方法的 room
。
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());
}
}
Public methods
Room handlers 有這些方法可用。
onMessage (type, callback)
注冊一個回調來處理客戶端發送的消息類型。
type
參數可以是 string
或 number
。
特定消息類型的回調
onCreate () {
this.onMessage("action", (client, message) => {
console.log(client.sessionId, "sent 'action' message: ", message);
});
}
回調所有消息
您可以注冊一個回調來處理所有其他類型的消息。
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);
});
}
setState (object)
設置新的 room state
實例。關於 state object
的更多細節,請參見 State Handling
。強烈建議使用新的 Schema Serializer
來處理您的 state
。
不要在 room state
下調用此方法進行更新。每次調用二進制補丁算法(binary patch algorithm
)時都會重新設置。
你通常只會在 room handler
的 onCreate()
期間調用這個方法一次。
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
則恢復為公開)。
Private rooms
沒有在 getAvailableRooms()
方法中列出。
setMetadata (metadata)
設置元數據(metadata
)到這個房間。每個房間實例都可以附加元數據 — 附加元數據的唯一目的是從客戶端獲取可用房間列表時將一個房間與另一個房間區分開來,使用 client.getAvailableRooms()
,通過它的 roomId
連接到它。
// 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 && 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()
代替
broadcast (type, message, options?)
向所有連接的客戶端發送消息。
可用的選項有:
except
: 一個Client
實例不向其發送消息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 });
});
}
只有在 state
發生變化后,才廣播消息給所有客戶端:
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 });
});
}
廣播一個 schema-encoded
的消息:
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
,則在提供的秒數后將取消重新連接。
async onLeave (client: Client, consented: boolean) {
// flag client as inactive for other users
this.state.players[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[client.sessionId].connected = true;
} catch (e) {
// 20 seconds expired. let's remove the client.
delete this.state.players[client.sessionId];
}
}
或者,您可以不提供 seconds
的數量來自動拒絕重新連接,而使用自己的邏輯拒絕它。
async onLeave (client: Client, consented: boolean) {
// flag client as inactive for other users
this.state.players[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[client.sessionId].connected = true;
} catch (e) {
// 20 seconds expired. let's remove the client.
delete this.state.players[client.sessionId];
}
}
disconnect ()
斷開所有客戶端,然后銷毀。
broadcastPatch ()
"你可能不需要這個!":該方法由框架自動調用。
該方法將檢查 state
中是否發生了突變,並將它們廣播給所有連接的客戶端。
如果你想控制什么時候廣播補丁,你可以通過禁用默認的補丁間隔來做到這一點:
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);
}
Public properties
roomId: string
一個唯一的,自動生成的,9
個字符長的 room id
。
您可以在 onCreate()
期間替換 this.roomId
。您需要確保 roomId
是唯一的。
roomName: string
您為 gameServer.define()
的第一個參數提供的 room
名稱。
state: T
您提供給 setState()
的 state
實例
clients: Client[]
已連接的客戶端 array
。參見 Web-Socket Client
。
maxClients: number
允許連接到房間的最大客戶端數。當房間達到這個限制時,就會自動鎖定。除非您通過 lock()
方法明確鎖定了房間,否則一旦客戶端斷開連接,該房間將被解鎖。
patchRate: number
將房間狀態發送到連接的客戶端的頻率(以毫秒為單位)。默認值為 50
ms(20fps)
autoDispose: boolean
當最后一個客戶端斷開連接時,自動銷毀房間。默認為 true
locked: boolean
(read-only)
在以下情況下,此屬性將更改:
- 已達到允許的最大客戶端數量(
maxClients
) - 您使用
lock()
或unlock()
手動鎖定或解鎖了房間
clock: ClockTimer
一個 ClockTimer
實例,用於 timing events
。
presence: Presence
presence
實例。查看 Presence API
了解更多信息。
Web-Socket Client
client
實例存在於:
Room#clients
Room#onJoin()
Room#onLeave()
Room#onMessage()
這是來自 ws
包的原始 WebSocket
連接。有更多可用的方法,但不鼓勵與 Colyseus 一起使用。
Properties
sessionId: string
每個會話唯一的 id。
在客戶端,你可以在 room
實例中找到 sessionId
auth: any
在 onAuth()
期間返回的自定義數據。
Methods
send(type, message)
發送一種 message
類型的消息到客戶端。消息是用 MsgPack
編碼的,可以保存任何 JSON-seriazeable
的數據結構。
type
可以是 string
或 number
。
發送消息:
//
// sending message with a string type ("powerup")
//
client.send("powerup", { kind: "ammo" });
//
// sending message with a number type (1)
//
client.send(1, { kind: "ammo"});
leave(code?: number)
強制斷開 client
與 room
的連接。
這將在客戶端觸發 room.onLeave
事件。
error(code, message)
將帶有 code
和 message
的 error
發送給客戶端。客戶端可以在 onError
上處理它。
對於 timing events,建議從您的 Room
實例中使用 this.clock
方法。
所有的間隔和超時注冊在 this.clock
。
當 Room
被清除時,會自動清除。
內置的 setTimeout
和
setInterval
方法依賴於 CPU 負載,這可能會延遲到意想不到的執行時間。
Clock
clock
是一種有用的機制,用於對有狀態模擬之外的事件進行計時。一個例子可以是:當玩家收集道具時,你可能會計時。您可以 clock.setTimeout
創建一個新的可收集對象。使用 clock.
的一個優點。您不需要關注 room
更新和增量,而可以獨立於房間狀態關注事件計時。
Public methods
注意:time
參數的單位是毫秒
clock.setInterval(callback, time, ...args): Delayed
setInterval()
方法重復調用一個函數或執行一個代碼片段,每次調用之間有固定的時間延遲。
它返回標識間隔的 Delayed
實例,因此您可以稍后對它進行操作。
clock.setTimeout(callback, time, ...args): Delayed
setTimeout()
方法設置一個 timer
,在 timer
過期后執行一個函數或指定的代碼段。它返回標識間隔的 Delayed
實例,因此您可以稍后對它進行操作。
示例
這個 MVP
示例顯示了一個 Room
:setInterval()
,setTimeout
和清除以前存儲的類型 Delayed
的實例; 以及顯示 Room's clock 實例中的 currentTime
。在1秒鍾的'Time now ' + this.clock.currentTime
被console.log
之后,然后10秒鍾之后,我們清除間隔:this.delayedInterval.clear();
。
// Import Delayed
import { Room, Client, Delayed } from "colyseus";
export class MyRoom extends Room {
// For this example
public delayedInterval!: Delayed;
// When room is initialized
onCreate(options: any) {
// start the clock ticking
this.clock.start();
// Set an interval and store a reference to it
// so that we may clear it later
this.delayedInterval = this.clock.setInterval(() => {
console.log("Time now " + this.clock.currentTime);
}, 1000);
// After 10 seconds clear the timeout;
// this will *stop and destroy* the timeout completely
this.clock.setTimeout(() => {
this.delayedInterval.clear();
}, 10_000);
}
}
clock.clear()
清除 clock.setInterval()
和 clock.setTimeout()
中注冊的所有間隔和超時。
clock.start()
開始計時。
clock.stop()
停止計時。
clock.tick()
在每個模擬間隔步驟都會自動調用此方法。在 tick
期間檢查所有 Delayed
實例。
參閱 Room#setSimiulationInterval()
了解更多信息。
Public properties
clock.elapsedTime
調用 clock.start()
方法后經過的時間(以毫秒為單位)。只讀的。
clock.currentTime
當前時間(毫秒)。只讀的。
clock.deltaTime
上一次和當前 clock.tick()
調用之間的毫秒差。只讀的。
Delayed
創建延遲的實例
clock.setInterval()
or clock.setTimeout()
Public methods
delayed.pause()
暫停特定的 Delayed
實例的時間。(elapsedTime
在 .resume()
被調用之前不會增加。)
delayed.resume()
恢復特定 Delayed
實例的時間。(elapsedTime
將繼續正常增長)
delayed.clear()
清除超時時間或間隔。
delayed.reset()
重置經過的時間(elapsed time
)。
Public properties
delayed.elapsedTime: number
Delayed
實例的運行時間,以毫秒為單位。
delayed.active: boolean
如果 timer
仍在運行,返回 true
。
delayed.paused: boolean
如果計時器通過 .pause()
暫停,則返回 true
。
Match-maker API
"您可能不需要這個!"
本節用於高級用途。通常使用 client-side methods
比較好。如果您認為您不能通過客戶端方法實現您的目標,您應該考慮使用本頁面中描述的方法。
下面描述的方法由 matchMaker
單例提供,可以從 "colyseus"
包中導入:
import { matchMaker } from "colyseus";
const matchMaker = require("colyseus").matchMaker;
.createRoom(roomName, options)
創建一個新房間
參數:
roomName
: 您在gameServer.define()
上定義的標識符。options
:onCreate
的選項。
const room = await matchMaker.createRoom("battle", { mode: "duo" });
console.log(room);
/*
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
*/
.joinOrCreate(roomName, options)
加入或創建房間並返回客戶端位置預訂。
參數:
roomName
: 您在gameServer.define()
上定義的標識符。options
: 客戶端位置預訂的選項(如onJoin
/onAuth
)。
const reservation = await matchMaker.joinOrCreate("battle", { mode: "duo" });
console.log(reservation);
/*
{
"sessionId": "zzzzzzzzz",
"room": { "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
}
*/
"消費位置預訂":您可以使用 consumeSeatReservation()
從客戶端開始通過預訂位置加入房間。
.reserveSeatFor(room, options)
在房間(room
)里為客戶端(client
)預訂位置。
"消費位置預訂":您可以使用 consumeSeatReservation()
從客戶端開始通過預訂位置加入房間。
參數:
room
: 房間數據 (結果來自createRoom()
等)options
:onCreate
選項
const reservation = await matchMaker.reserveSeatFor("battle", { mode: "duo" });
console.log(reservation);
/*
{
"sessionId": "zzzzzzzzz",
"room": { "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
}
*/
.join(roomName, options)
加入房間並返回位置預訂。如果沒有可用於 roomName
的房間,則拋出異常。
參數:
roomName
: 您在gameServer.define()
上定義的標識符。options
: 客戶端位置預訂的選項(用於onJoin
/onAuth
)
const reservation = await matchMaker.join("battle", { mode: "duo" });
console.log(reservation);
/*
{
"sessionId": "zzzzzzzzz",
"room": { "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
}
*/
"消費位置預訂":您可以使用 consumeSeatReservation()
從客戶端開始通過預訂位置加入房間。
.joinById(roomId, options)
按 id
加入房間並返回客戶端位置預訂。如果沒有為 roomId
找到 room
,則會引發異常。
參數:
roomId
: 特定room
實例的ID
。options
: 客戶端位置預訂的選項(用於onJoin
/onAuth
)
const reservation = await matchMaker.joinById("xxxxxxxxx", {});
console.log(reservation);
/*
{
"sessionId": "zzzzzzzzz",
"room": { "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
}
*/
"消費位置預訂":您可以使用 consumeSeatReservation()
從客戶端開始通過預訂位置加入房間。
.create(roomName, options)
創建一個新的房間並返回客戶端位置預訂。
參數:
roomName
: 你在gameServer.define()
上定義的標識符。options
: 客戶端位置預訂的選項(用於onJoin
/onAuth
)
const reservation = await matchMaker.create("battle", { mode: "duo" });
console.log(reservation);
/*
{
"sessionId": "zzzzzzzzz",
"room": { "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
}
*/
"消費位置預訂":您可以使用 consumeSeatReservation()
從客戶端開始通過預訂位置加入房間。
.query(conditions)
對緩存的房間執行查詢。
const rooms = await matchMaker.query({ name: "battle", mode: "duo" });
console.log(rooms);
/*
[
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false },
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false },
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
]
*/
.findOneRoomAvailable(roomName, options)
尋找一個可用公開的和沒上鎖的房間
參數:
roomId
: 特定room
實例的ID
。options
: 客戶端位置預訂的選項(用於onJoin
/onAuth
)
const room = await matchMaker.findOneRoomAvailable("battle", { mode: "duo" });
console.log(room);
/*
{ "roomId": "xxxxxxxxx", "processId": "yyyyyyyyy", "name": "battle", "locked": false }
*/
.remoteRoomCall(roomId, method, args)
在遠程 room
中調用一個方法或返回一個屬性。
參數:
roomId
: 特定room
實例的ID
。method
: 要調用或檢索的方法或屬性。args
: 參數數組。
// call lock() on a remote room by id
await matchMaker.remoteRoomCall("xxxxxxxxx", "lock");
Presence
當需要在多個進程和/或機器上擴展服務器時,需要向 Server
提供 Presence
選項。Presence
的目的是允許不同進程之間通信和共享數據,特別是在配對(match-making
)過程中。
LocalPresence
(default)RedisPresence
每個 Room
處理程序上也可以使用 presence
實例。您可以使用它的 API
來持久化數據,並通過 PUB/SUB
在房間之間通信。
LocalPresence
這是默認選項。它用於在單個進程中運行 Colyseus
時使用。
RedisPresence (clientOpts?)
當您在多個進程和/或機器上運行 Colyseus
時,請使用此選項。
Parameters:
clientOpts
: Redis 客戶端選項(host/credentials)。查看選項的完整列表。
import { Server, RedisPresence } from "colyseus";
// This happens on the slave processes.
const gameServer = new Server({
// ...
presence: new RedisPresence()
});
gameServer.listen(2567);
const colyseus = require('colyseus');
// This happens on the slave processes.
const gameServer = new colyseus.Server({
// ...
presence: new colyseus.RedisPresence()
});
gameServer.listen(2567);
API
Presence
API 高度基於 Redis 的 API,這是一個鍵值數據庫。
每個 Room
實例都有一個 presence
屬性,該屬性實現以下方法:
subscribe(topic: string, callback: Function)
訂閱給定的 topic
。每當在 topic
上消息被發布時,都會觸發 callback
。
unsubscribe(topic: string)
退訂給定的topic
。
publish(topic: string, data: any)
將消息發布到給定的 topic
。
exists(key: string): Promise<boolean>
返回 key
是否存在的布爾值。
setex(key: string, value: string, seconds: number)
設置 key
以保留 string
值,並將 key
設置為在給定的秒數后超時。
get(key: string)
獲取 key
的值。
del(key: string): void
刪除指定的 key
。
sadd(key: string, value: any)
將指定的成員添加到存儲在 key
的 set
中。已經是該 set
成員的指定成員將被忽略。如果 key
不存在,則在添加指定成員之前創建一個新 set
。
smembers(key: string)
返回存儲在 key
中的 set
值的所有成員。
sismember(member: string)
如果 member
是存儲在 key
處的 set
的成員,則返回
Return value
1
如果元素是set
中的元素。0
如果元素不是set
的成員,或者key
不存在。
srem(key: string, value: any)
從 key
處存儲的 set
中刪除指定的成員。不是該 set
成員的指定成員將被忽略。如果 key
不存在,則將其視為空set
,並且此命令返回 0
。
scard(key: string)
返回 key
處存儲的 set
的 set
基數(元素數)。
sinter(...keys: string[])
返回所有給定 set
的交集所得的 set
成員。
hset(key: string, field: string, value: string)
將 key
存儲在 hash
中的字段設置為 value
。如果 key
不存在,則創建一個包含 hash
的新 key
。如果字段已經存在於 hash
中,則將覆蓋該字段。
hincrby(key: string, field: string, value: number)
以增量的方式遞增存儲在 key
存儲的 hash
中的字段中存儲的數字。如果 key
不存在,則創建一個包含 hash
的新 key
。如果字段不存在,則在執行操作前將該值設置為 0
。
hget(key: string, field: string): Promise<string>
返回與存儲在 key
處的 hash
中的 field
關聯的值。
hgetall(key: string): Promise<{[field: string]: string}>
返回存儲在 key
處的 hash
的所有字段和值。
hdel(key: string, field: string)
從存儲在 key
處的 hash
中刪除指定的字段。該 hash
中不存在的指定字段將被忽略。如果 key
不存在,則將其視為空 hash
,並且此命令返回 0
。
hlen(key: string): Promise<number>
返回 key
處存儲的 hash
中包含的字段數
incr(key: string)
將存儲在 key
值上的數字加 1
。如果 key
不存在,則將其設置為 0
,然后再執行操作。如果 key
包含錯誤類型的值或包含不能表示為整數的字符串,則返回錯誤。該操作僅限於 64
位有符號整數。
decr(key: string)
將存儲在 key
中的數字減 1
。如果 key
不存在,則將其設置為 0
,然后再執行操作。如果 key
包含錯誤類型的值或包含不能表示為整數的字符串,則返回錯誤。該操作僅限於 64
位有符號整數。
Graceful Shutdown
Colyseus 默認提供優雅的關閉機制。這些操作將在進程殺死自己之前執行:
- 異步斷開所有已連接的客戶端 (
Room#onLeave
) - 異步銷毀所有生成的房間 (
Room#onDispose
) - 在關閉進程
Server#onShutdown
之前執行可選的異步回調
如果您要在 onLeave
/ onDispose
上執行異步任務,則應返回 Promise
,並在任務准備就緒時 resolve
它。 onShutdown(callback)
也是如此。
Returning a Promise
通過返回一個 Promise
,服務器將在殺死 worker
進程之前等待它們完成。
import { Room } from "colyseus";
class MyRoom extends Room {
onLeave (client) {
return new Promise((resolve, reject) => {
doDatabaseOperation((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
onDispose () {
return new Promise((resolve, reject) => {
doDatabaseOperation((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
}
使用 async
async
關鍵字將使函數在底層返回一個 Promise
。閱讀更多關於Async / Await的內容。
import { Room } from "colyseus";
class MyRoom extends Room {
async onLeave (client) {
await doDatabaseOperation(client);
}
async onDispose () {
await removeRoomFromDatabase();
}
}
進程關閉回調
你也可以通過設置 onShutdown
回調來監聽進程關閉。
import { Server } from "colyseus";
let server = new Server();
server.onShutdown(function () {
console.log("master process is being shut down!");
});
Refs
中文手冊同步更新在:
- https:/colyseus.hacker-linner.com
我是為少
微信:uuhells123
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)