ColyseusJS 輕量級多人游戲服務器開發框架 - 中文手冊(中)


快速上手多人游戲服務器開發。后續會基於 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.authorizedreq.connection.encrypted 被設置。
  • next (Function) 用戶必須在檢查 info 字段后調用該回調。此回調中的參數為:

    • result (Boolean) 是否接受握手。
    • code (Number) 當 resultfalse 時,該字段決定發送給客戶端的 HTTP 錯誤狀態碼。
    • name (String) 當 resultfalse 時,該字段決定 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 方法(joinjoinOrCreate 等):

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 方法。

當客戶端成功加入房間時,在 requestJoinonAuth 成功后調用。

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

這個例子演示了一個實現 onCreateonJoinonMessage 方法的 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 參數可以是 stringnumber

特定消息類型的回調

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 handleronCreate() 期間調用這個方法一次。

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

將房間狀態發送到連接的客戶端的頻率(以毫秒為單位)。默認值為 50ms(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 可以是 stringnumber

發送消息:

//
// 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)

強制斷開 clientroom 的連接。

這將在客戶端觸發 room.onLeave 事件。

error(code, message)

將帶有 codemessageerror 發送給客戶端。客戶端可以在 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 示例顯示了一個 RoomsetInterval()setTimeout 和清除以前存儲的類型 Delayed 的實例; 以及顯示 Room's clock 實例中的 currentTime。在1秒鍾的'Time now ' + this.clock.currentTimeconsole.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:

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)

將指定的成員添加到存儲在 keyset 中。已經是該 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 處存儲的 setset 基數(元素數)。

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
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM