
快速上手多人游戏服务器开发。后续会基于 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
公众号:黑客下午茶
加我微信(互相学习交流),关注公众号(获取更多学习资料~)