你的第一个 Client Engine 小游戏 · Node.js
该文档帮助您快速上手,通过 Client Engine 实现一个剪刀石头布的猜拳小游戏。完成本文档教程后,您会对 Client Engine 的基础使用流程有初步的理解。
准备初始项目
这个小游戏分为服务端和客户端两部分,其中服务端使用 Client Engine 来实现,客户端则是一个简单的 Web 页面。在这个教程中我们着重教您一步一步写 Client Engine 中的代码,客户端的代码请您查看示例项目。
Client Engine 项目
请先阅读 Client Engine 快速入门:运行及部署项目 获得初始项目,了解如何本地运行及部署项目。
./src 中主要源文件及用途如下:
├── configs.ts // 配置文 件
├── index.ts // 项目入口
├── reception.ts // Reception 类实现文件,GameManager 的子类,负责管理 Game,在这个文件中撰写了创建 Game 的自定义方法
└── rps-game.ts // RPSGame 类实现文件,Game 的子类,在这个文件中撰写了具体猜拳游戏的逻辑
该项目中的 Game 及 GameManager 使用的是 Client Engine SDK 的功能,关于 SDK 的详细用法请参考 Client Engine 开发指南。
您可以从 index.ts 文件入手来了解整个项目,该文件是项目启动的入口,它通过 express 框架定义了名为 /reservation 的 Web API,供客户端快速开始时为客户端下发新的房间名称。
reception.ts 及 rps-game.ts 里面有本教程的全部代码。您可以选择备份这两个文件,清空这两个文件后根据本文档撰写自己的代码,同时也可以查看已经写好的代码以做对比。
客户端项目
点击下载客户端项目。打开 ./src 中的 config.ts,将 appId 和 appKey 修改为自己应用的信息,按照 README 启动项目后观察界面的变化。游戏相关的逻辑位于 ./src/components 下的文件中,在有需要的时候您可以打开这里的文件查看代码。
核心流程
在多人对战服务中,房间的创建者为 MasterClient,因此在这个小游戏中,每一个房间都是由 Client Engine 管理的 MasterClient 调用在线对战服务相关的接口来创建的。Client Engine 中会有多个 MasterClient,每一个 MasterClient 管理着自己房间内的游戏逻辑。
这个小游戏的核心逻辑为:**Client Engine 中的 MasterClient 及客户端玩家 Client 加入到同一个房间,在通信过程中由 MasterClient 控制游戏内的逻辑。**具体拆解步骤如下:
- 玩家客户端连接多人在线对战服务,向 Client Engine 提供的
/reservation接口请求快速开始游戏。 - Client Engine 每次收到请求后会检查是否有可用的房间,如果有则返回已有的 roomName 给客户端;如果没有则创建新的 MasterClient 并创建一个新的房间,返回 roomName 给客户端。
- 客户端通过 Client Engine 返回的 roomName 加入房间。
- MasterClient 和客户端在同一房间内,每次客户端出拳时会将消息发送给 MasterClient,MasterClient 将消息转发给其他客户端,并最终判定游戏结果。
- MasterClient 判定游戏结束,客户端离开房间,Client Engine 销毁游戏。
代码开发
自定义 Game
我们的目标是让 MasterClient 和客户端 Client 进入同一个房间,第一步在 Client Engine 中我们先准备好房间。在 Client Engine SDK 中,每一个房间都对应一个 Game 对象,每一个 Game 对象都对应一个自己的 MasterClient。接下来我们创建一个继承 Game 的子类 RPSGame ,在 RPSGame 中撰写猜拳小游戏的房间内逻辑。
在 rpg-game.ts 文件中初始化自定义的 RPSGame:
import { Game } from "@leancloud/client-engine";
import { Event, Play, Room } from "@leancloud/play";
export default class RPSGame extends Game {
constructor(room: Room, masterClient: Play) {
super(room, masterClient);
}
}
管理 Game
Client Engine SDK 中,GameManager 负责 Game 的创建及销毁,具体的原理及结构介绍请参考 Client Engine 开发指南。在这篇文档中,我们通过简单的配置就可以使用 GameManager 的管理功能。
自定义 GameManager
首先创建一个子类 Reception 继承自 GameManager,在这个子类中我们就可以使用 GameManager 提供的方法来帮我们撰写自己的逻辑。
在 reception.ts 文件中初始化自定义的 Reception:
import { Game, GameManager, ICreateGameOptions } from "@leancloud/client-engine";
export default class Reception<T extends Game> extends GameManager<T> {
}
这个自定义的类Reception 用于管理 T 类型的 Game 对象,在实际游戏中会是您自定义的 Game 类型的实例。接下来,我们在 reception 中使用 GameManager 的方法来实现自己的自定义逻辑:快速开始。
实现逻辑:「快速开始」
这里我们要实现的快速开始的逻辑是:随便找一个有空位的房间返回给客户端,如果当前的 Client Engine 实例没有可用的房间,那么就创建一个房间返回给客户端。我们在 Reception 类中撰写名为 makeReservation() 的自定义方法来实现这个逻辑并供[入口 API ](#入口 API:快速开始)调用。
import { Game, GameManager, ICreateGameOptions } from "@leancloud/client-engine";
export default class Reception<T extends Game> extends GameManager<T> {
/**
* 为指定玩家预约游戏,如果没有可用的游戏会创建一个新的游戏。
* @param playerId 预约的玩家 ID
* @return 预约成功的游戏的房间 name
*/
public async makeReservation(playerId: string) {
let game: T;
const availableGames = this.getAvailableGames();
if (availableGames.length > 0) {
game = availableGames[0];
this.reserveSeats(game, playerId);
} else {
game = await this.createGame(playerId);
}
return game.room.name;
}
}
在这段代码中,我们调用了 GameManager 的 getAvailableGames() 来获得当前 Client Engine 实例管理的 Game:
- 如果有房间内还有空位的
Game,使用GameManager的reserveSeats()方法为玩家占位并返回 roomName。 - 如果所有
Game房间都已经满员了,使用GameManager的createGame()方法创建一个新的房间并返回 roomName。
实现逻辑:「创建新游戏」
如果您希望自己先创建房间,再邀请朋友加入该房间,可以在 reception 中写一个创建新游戏的方法供供[入口 API ](#入口 API:创建新游戏)调用。同样我们自定义的 createGameAndGetName() 方法内用到的 createGame() 是由 SDK 中的 GameManager 提供的。
export default class Reception<T extends Game> extends GameManager<T> {
public async makeReservation(playerId: string) {
......
}
/**
* 创建一个新的游戏。
* @param playerId 预约的玩家 ID
* @param options 创建新游戏时可以指定的一些配置项
* @return 创建的游戏的房间 name
*/
public async createGameAndGetName(playerId: string, options?: ICreateGameOptions) {
const game = await this.createGame(playerId, options);
return game.room.name;
}
}
绑定 GameManager 及 Game
当 GameManager 的子类 Reception 及 Game 的子类 RPSGame 都准备好后,我们要在整个项目入口把 RPSGame 给到 Reception,由 Reception 来管理 RPSGame。
在 index.ts 文件创建 Reception 对象的方法中可以看到,第一个参数已经传入了 RPSGame,如果您的自定义 Game 使用的是其他的名字,可以将 RPSGame 换成您自定义的 Game 类。
import PRSGame from "./rps-game";
const reception = new Reception(
PRSGame,
APP_ID,
APP_KEY,
{
concurrency: 2,
},
);
在这里配置完成后,reception 会在合适的时机创建并管理 PRSGame 和对应的 MasterClient。