快速上手多人遊戲伺服器開發。後續會基於 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
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)