当所有玩家都离开后,GameManager 会自动帮您销毁当前房间及相关的 MasterClient。此时如果您没有其他的逻辑要做,则不需要关心本节文档。如果您希望自己做一些清理工作,例如保存用户数据等,可以使用 autoDestroy 装饰器,这个装饰器会在所有玩家离开后自动触发 Game 子类中的 destroy() 方法,您可以将相关逻辑写在这个方法中。
import { autoDestroy, Game } from "@leancloud/client-engine";
@autoDestroy()
export default class SampleGame extends Game {
protected destroy() {
super.destroy();
console.log('在这里可以做额外的清理工作');
}
}
负载均衡
Client Engine 会根据整体实例负载的高低自动对实例数量进行调整。
在 Client Engine 中,需要负载均衡的情况有两种:第一种是客户端通过 REST API 发起的请求,第二种是每一个实例运行的 Game 数量的负载。对于客户端通过 REST API 发起的请求,Client Engine 会自动将请求均匀的分配给当前的所有实例,不需要我们再做任何配置工作。对于第二种情况,每个 Game 对象(每局游戏)一般都会持续存在一段时间,为了让每个实例承载的 Game 对象尽可能达到均衡,我们需要额外配置 GameManager 到负载均衡系统中。
Client Engine 开发指南 · Node.js
请先阅读 Client Engine 快速入门 · Node.js 及你的第一个 Client Engine 小游戏来初步了解如何使用初始项目来开发游戏。本文档将在初始项目的基础上深入讲解 Client Engine SDK。
Client Engine 初始项目依赖了专门的 Client Engine SDK, Client Engine SDK 在多人在线对战 SDK 的基础上进行了封装,帮助您更好的撰写服务端游戏逻辑。您可以通过快速入门安装依赖。
组件
SDK 提供以下组件:
Game:负责房间内游戏的具体逻辑。Client Engine 维护了许多游戏房间,每一个游戏房间都是一个 Game 实例,即每个 Game 实例对应一个唯一的 Play Room 与 MasterClient。游戏房间内的逻辑由 Game 中的代码来控制,因此房间内的游戏逻辑必须继承自该类。GameManager:负责创建、管理及分配具体的 Game 对象。Game 的管理及销毁由 SDK 负责,不需要您再自己额外写代码。GameManager
GameManager 实例化
GameManager会帮您自动创建、管理并销毁 Game,因此在项目启动时,您需要实例化GameManager。相关示例代码如下:自定义 GameManager
首先需要自定义一个 Class 继承自
GameManager,例如示例代码中的SampleGameManager:在 GameManager 中自定义方法
Client Engine 的核心用法之一是负责创建 Game 并返回 roomName 给客户端,因此在
SampleGameManager这个类中,我们需要撰写创建Game的方法提供给 Web API 使用。例如示例项目中的快速开始和创建新游戏。这里的示例代码我们以创建新游戏为例:写完自定义方法之后,在下文我们还需要为这里的方法配置负载均衡,需要注意的是,受负载均衡系统的要求,
GameManager子类中的public方法,其参数与返回值必须是string、number、boolean、null、Object、Array中的一种。以上代码中可以看到GameManager的createGame()方法返回的是一个Game,不符合负载均衡的要求,因此我们在这里封装为自己的方法createGameAndGetName()。创建 GameManager 子类对象
接下来创建
GameManager的子类对象,在创建SampleGameManager的时候,需要在第一个参数内传入自定义的 Game ,这里使用的是示例 Demo 猜拳游戏中的RPSGame。设置负载均衡
GameManager需要配置负载均衡,以确保GameManager创建的Game能够尽可能均匀分配到每一个 Client Engine 实例中。负载均衡的详细文档请看下文,在这里,我们先讲解如何配置。在这里我们创建一个负载均衡对象,然后将上面的
gameManager绑定到负载均衡中:loadBalancerFactory的bind()方法中,第一个参数是gameManager对象,第二个参数是一个数组,传入需要进行负载均衡的方法名["createGameAndGetName"]。到这里,
gameManager的配置就完成了,您可以在自己定义的 Web API 处这样调用相关方法:gameManager.createGameAndGetName()。创建房间
在 GameManager 实例化这一节中,我们在子类中使用了
GameManager的createGame()来创建房间。createGame()接受以下参数:customRoomProperties,customRoomPropertyKeysForLobby,visible,对这三个参数的说明请参考创建房间。minSeatCount和maxSeatCount之间,否则 Client Engine 会拒绝创建房间。如果不指定,则以defaultSeatCount为准。例如创建一个带有匹配条件的新房间时,可以这样调用
createGame():在你的第一个 Client Engine 小游戏中,
reception.ts中使用createGame()撰写了两个自定义方法用于「快速开始」和「创建新游戏」,并通过index.ts中的 Web API/reservation和/game来调用相关逻辑,如果没有自定义需求您可以直接使用示例 Demo 中的接口并传入以上参数。获取当前可用的房间
GameManager 提供了
getAvailableGames()方法来获取当前 GameManager 对象所在的 Client Engine 实例中的可用游戏列表。这里的可用指的是房间还有空位。使用示例代码如下:需要注意的是,这个方法获取的不是多人对战服务中所有的可用房间,仅限于当前所在 Client Engine 实例中的可用房间,Client Engine 多实例负载均衡请参考负载均衡。
匹配
GameManager暂时没有提供匹配机制,如果客户端只需要随机加入某个房间,可以参考示例项目中「快速开始」的实现方案。这个实现方案会在负载最低的实例中寻找可用房间或创建房间,最终返回给客户端一个可加入的房间名称。如果您希望实现有条件的匹配,可以这样实现:
/game入口。这个流程在客户端中的示例代码如下(非 Client Engine):
客户端首先向多人对战服务发起有条件的加入房间请求:
如果多人对战服务此时有可以加入的新房间,您会自动加入到新房间中,并触发加入房间成功事件:
如果没有可以加入的房间,会触发加入房间失败事件。在这个事件中,4301 错误码代表着没有可以加入的空房间,此时我们向 Client Engine 请求创建一个新的房间,获得新房间的 roomName 后加入新房间:
Game
Game 生命周期
Game由 SDK 中的GameManager管理,GameManager会在收到创建房间的请求时根据情况创建Game。Game的控制权从 SDK 中GameManager移交给Game本身。从这个时刻开始,会有玩家陆续加入游戏房间。Game将控制权交回GameManager,GameManager做最后的清理工作,包括断开并销毁该房间的 masterClient、将Game从管理的游戏列表中删除等。Game 通用属性
在实现游戏逻辑的过程中,
Game类提供下面这些属性来简化常见需求的实现,您可以在继承Game的自己的类中方便的获得以下属性:room属性:游戏对应的房间,这是一个 Play SDK 中的 Room 实例。masterClient属性:游戏对应的 masterClient,这是一个 Play SDK 中的 Play 实例。players属性:不包含 masterClient 的玩家列表。注意,如果您通过 Play SDK Room 实例的playerList属性获取的房间成员列表是包括 masterClient 的。Game 通用方法
Game 类在多人对战 SDK 的基础上封装了以下方法,使得 MasterClient 可以更便利的发送自定义事件:
broadcast()方法:向所有玩家广播自定义事件。示例代码请参考广播自定义事件。forwardToTheRests()方法:将一个玩家发送的自定义事件转发给其他玩家。示例代码请参考转发自定义事件。实现自己的 Game
实现自己的房间内游戏逻辑时,您需要创建一个继承自
Game的类来撰写自己的游戏逻辑,示例方法如下:设置房间内玩家数量
这里的玩家数量指的是不包括 MasterClient 的玩家数量,根据多人对战服务的限制,最多不能超过 9 个人。
在
Game中需要指定defaultSeatCount静态属性作为默认的玩家数量,Client Engine 会根据这个值向多人对战服务请求创建房间。例如斗地主需要 3 个人才能玩,可以这样设置:如果您的游戏需要的玩家数量在某个范围内,除了设置
defaultSeatCount外,还需要使用minSeatCount静态属性限定最小玩家数量,maxSeatCount静态属性设定最大玩家数量。例如三国杀要求至少 2 个人,最多 8 个人才能玩,默认 5 个人可以玩,可以这样设置:在创建房间的接口中,可以将客户端 request 请求中的
seatCount参数来动态覆盖掉defaultSeatCount。当房间人数达到
seatCount时,您可以选择配置触发房间人满事件,如果您的客户端没有指定seatCount,人满事件时将以defaultSeatCount的值为准。加入房间事件
当客户端成功加入房间后,位于 Client Engine 的 MasterClient 会收到新玩家加入事件,如果您需要监听此事件,可以在自定义的
Game中的constructor()方法中撰写监听的代码:房间人满事件
当房间的人数满足设置房间内玩家数量的人满逻辑时,
watchRoomFull装饰器会让您收到 Game 抛出的AutomaticGameEvent.ROOM_FULL事件,您可以在这个事件中撰写相应的游戏逻辑,例如关闭房间,向客户端广播游戏开始:广播自定义事件
在房间人满事件中,
Game向房间内所有成员广播了游戏开始:在广播事件时您还可以带有一些数据:
此时客户端的接收自定义事件方法会被触发,如果发现是
game-start事件,客户端可以在 UI 上展示对战开始。转发自定义事件
MasterClient 可以转发某个客户端发来的事件给其他客户端,在转发时还可以处理数据:
在这个代码中,
event参数是某个客户端发来的原始事件,eventData是原始事件的数据,您可以在转发事件给其他客户端时处理该数据,例如抹去或增加一些信息。MasterClient 发送该事件后,客户端的接收自定义事件会被触发。MasterClient 与客户端通信
除了上方初始项目提供的广播自定义事件及转发自定义事件外,您依然可以使用多人对战服务中的自定义属性、自定义事件进行通信。
除此之外,
Game还提供了以下 RxJS 方法方便您对事件进行流处理,进而精简自己的代码及逻辑:getStream()方法:获取玩家发送的自定义事件的流,这是一个 RxJS 中的 Observable 对象。接口说明请参考 API 文档。takeFirst()方法:获取玩家发送的指定条件的从现在开始算的第一条自定义事件的流,返回一个 RxJS 中的 Observable 对象。接口说明请参考 API 文档。注意,以上两个方法需要您了解 RxJS 才能使用,如果您不了解 RxJS,依然可以使用多人对战服务中的事件方法进行通信。
游戏结束
当所有玩家都离开后,
GameManager会自动帮您销毁当前房间及相关的 MasterClient。此时如果您没有其他的逻辑要做,则不需要关心本节文档。如果您希望自己做一些清理工作,例如保存用户数据等,可以使用autoDestroy装饰器,这个装饰器会在所有玩家离开后自动触发Game子类中的destroy()方法,您可以将相关逻辑写在这个方法中。负载均衡
Client Engine 会根据整体实例负载的高低自动对实例数量进行调整。
在 Client Engine 中,需要负载均衡的情况有两种:第一种是客户端通过 REST API 发起的请求,第二种是每一个实例运行的
Game数量的负载。对于客户端通过 REST API 发起的请求,Client Engine 会自动将请求均匀的分配给当前的所有实例,不需要我们再做任何配置工作。对于第二种情况,每个Game对象(每局游戏)一般都会持续存在一段时间,为了让每个实例承载的Game对象尽可能达到均衡,我们需要额外配置GameManager到负载均衡系统中。这个特性由 SDK 提供的
LoadBalancerFactory类实现。在 GameManager 实例化中我们可以看到,LoadBalancerFactory通过绑定gameManager生成一个LoadBalancer的对象,每一个 Client Engine 实例中都会有这样一个对象。当 Client Engine 的某个实例接收到来自客户端的 REST API 请求,并调用
gameManager中的方法时,接收请求的实例中的负载均衡节点LoadBalancer会找出集群中承载Game数量最小的实例,将指定的gameManager的 API 调用转发给该实例的gameManager运行并将结果返回。在这里,LoadBalancer只负责请求的转发,不关心如何处理请求。API 文档
您可以在 API 文档中找到更多 SDK 的类、方法及属性说明,点击查看 Client Engine SDK API 文档。