多人在线对战开发指南 · C#
前言
多人在线对战是一款基于 C# 编写的游戏 SDK,它为有强联网需求的网 络游戏提供了一整套的客户端 SDK 解决方案,因此开发团队不再需要自建服务端,从而节省大部分开发和运维成本。多人在线对战提供的主要功能如下:
- 获取房间列表
- 创建房间
- 加入房间
- 随机加入(符合条件的)房间
- 获取房间玩家
- 获取、设置、同步房间的属性
- 获取、设置、同步玩家的属性
- 发送和接收「自定义事件」
- 离开房间
SDK 导入
请阅读 安装指南,获取 dll 库文件。
初始化
导入需要的命名空间。
using LeanCloud.Play;
接着我们需要实例化一个在线对战 SDK 的客户端对象。
var client = new Client(
"your-client-id", // 游戏的 Client ID
"your-client-token", // 游 戏的 Client Token
"tarara", // 设置用户 id
playServer: "https://your_server_url", // 游戏的 API 域名
gameVersion: "0.0.1" // 设置游戏版本号,选填,默认 0.0.1,不同版本的玩家不会匹配到同一个房间
);
- 在 开发者中心 > 你的游戏 > 游戏服务 > 应用配置 可以查看游戏的
Client ID和Client Token。 - API 域名在 应用配置 > 域名配置 > API 处查看,参考文档关于域名的说明。
其中,
userId 作为客户端的唯一标识连接至服务器。
需要注意,这个 userId 有如下限制:
- 只允许英文、数字与下划线
- 长度不能超过 32 个字符
- 一个应用内全局唯一
gameVersion 表示客户端的版本号,如果允许多个版本的游戏共存,则可以根据这个版本号路由到不同的游戏服务器。
连接
建立连接
通过下面的代码将当前玩家连接到多人对战服务:
try {
await client.Connect();
// 连接成功
} catch (PlayException e) {
// 连接失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
大厅
我们推荐您**「不要」将玩家加入大厅**,因为在大厅中,服务端会不停地下发最新的全量房间列表,由玩家自行选择其中一个房间进行游戏,这种方式不仅对玩家体验不友好,同时来会带来很大的带宽压力。 我们推荐您像现在的绝大部分手游一样,直接通过房间匹配的方式,快速匹配开始游戏。
如果您有特殊的游戏场景需要获取房间列表,可以调用以下方法:
try {
await client.JoinLobby();
// 加入大厅成功
} catch (PlayException e) {
// 加入大厅失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
当玩家加入到大厅后,服务端会将当前大厅的房间列表推送给客户端,开发者可以根据需求显示房间列表,或加入房间参与游戏。
client.OnLobbyRoomListUpdated += () => {
var roomList = client.LobbyRoomList;
// TODO 可以做房间列表展示的逻辑
};
更多关于 LobbyRoom,请参考 API 文档。
相关事件
| 事件 | 参数 | 描述 |
|---|---|---|
| OnLobbyRoomListUpdated | 无 | 大厅房间列表更新 |
房间匹配
房间,是指产生玩家「战斗交互」的单位。比如斗地主的牌桌、MMO 的副本、微信游戏的对战等,广义上都属于房间的范畴。 玩家之间的战斗交互都是在房间内完成的。所以,玩家「如何进入房间」就成了房间匹配的关键。下面我们将从「创建房间」和「加入房间」两方面来分析一下常用的「房间匹配」功能。
创建房间
我们可以这样简单地创建一个房间。创建房间的玩家为房主(MasterClient),房主在创建房间成功的同时也意味着成功加入了该房间。
try {
await client.CreateRoom();
// 创建房间成功也意味着自己已经成功加入了该房间
} catch (PlayException e) {
// 创建房间失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
我们也可以创建一个设定相关信息的房间。
// 房间的自定义属性
var props = new PlayObject {
{ "title", "room title" },
{ "level", 2 }
};
var roomOptions = new RoomOptions {
Visible = false,
EmptyRoomTtl = 10000,
PlayerTtl = 600,
MaxPlayerCount = 2,
CustomRoomProperties = props,
CustomRoomPropertyKeysForLobby = new List<string> { "level" },
Flag = CreateRoomFlag.MasterSetMaster | CreateRoomFlag.MasterUpdateRoomProperties,
};
var expectedUserIds = new List<string> { "cr3_2" };
try {
await client.CreateRoom(roomName, roomOptions, expectedUserIds);
// 创建房间成功也意味着自己已经成功加入了该房间
} catch (PlayException e) {
// 创建房间失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
其中 roomName、roomOptions 和 expectedUserIds 这些参数都是可选参数。
roomName
房间名称必须保证唯一;如果不设置,则由服务端生成唯一房间 Id 并返回。
roomOptions
创建房间时的指定参数,包括:
Opened:房间是否开放。如果设置为 false,则不允许其他玩家加入。Visible:房间是否可见。如果设置为 false,则不会出现在大厅的房间列表中,但是其他玩家可以通过指定「房间名称」加入房间。EmptyRoomTtl:当房间中没有玩家时,房间保留的时间(单位:秒)。默认为 0,即 房间中没有玩家时,立即销毁房间数据。最大值为 300,即 5 分钟。PlayerTtl:当玩家掉线时,保留玩家在房间内的数据的时间(单位:秒)。默认为 0,即 玩家掉线后,立即销毁玩家数据。最大值为 300,即 5 分钟。MaxPlayerCount:房间允许的最大玩家数量。CustomRoomProperties:房间的自定义属性。CustomRoomPropertyKeysForLobby:房间的自定义属性CustomRoomProperties中「键」的数组,包含在CustomRoomPropertyKeysForLobby中的属性将会出现在大厅的房间属性中(client.LobbyRoomList),而全部属性要在加入房间后的room.CustomProperties中查看。这些属性将会在匹配房间时用到。Flag:创建房间标志位,详情请看下文中 MasterClient 掉线不转移,指定其他成员为 MasterClient,只允许 MasterClient 修改房间属性。
expectedUserIds
指定的玩家 ID 数组。这个参数主要用于为某些能加入到房间中的特定玩家「占位」。
注意:这些「特定的玩家」并不会真地加入到房间里来,而只会在房间的「空位」上预留出位置,只允许「特定的玩家」加入。如果开发者要做「邀请加入」的功能,还需要通过其他途径,例如 IM、微信分享等将「房间名称」发送给好友,好友再通过 JoinRoom(roomName) 接口加入房间。
更多关于 CreateRoom,请参考 API 文档。
加入房间
当房间创建好后,其他的玩家可以通过「加入房间」参与到游戏中。
加入指定房间
通过指定「房间名称」加入房间。
try {
// 玩家在加入 'LiLeiRoom' 房间
await client.JoinRoom("LiLeiRoom");
// 加入房间成功
} catch (PlayException e) {
// 加入房间失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
在加入房间时,也可以为其他玩家占位,如果房间剩余空位小于占位数量,会加入房间失败。
var expectedUserIds = new List<string>() { "LiLei", "Jim" };
try {
// 玩家在加入 "game" 房间,并为 LiLei 和 Jim 玩家占位
await client.JoinRoom("game", expectedUserIds);
} catch (PlayException e) {
// 加入房间失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
更多关于 JoinRoom,请参考 API 文档。
随机加入房间
有时候,我们不需要加入指定某个房间,而是随机加入「符合某些条件的房间」(甚至可以是没有条件),比如快速开始、快速匹配等,这时我们可以通过调用 JoinRandomRoom 方法随机加入房间。
try {
await client.JoinRandomRoom();
// 加入房间成功
} catch (PlayException e) {
// 加入房间失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
也可以为随机加入设置「条件」,例如随机加入到 level = 2 的房间。
// 设置匹配属性
var matchProps = new PlayObject {
{ "level", 2 }
};
try {
await client.JoinRandomRoom(matchProps);
} catch (PlayException e) {
// 加入房间失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
加入或创建指定房间
很多游戏没有强制指定房主的场景,只要一部分玩家能进入到同一个房间游戏,谁作为房主都可以,这时我们可以用 JoinOrCreateRoom() 来实现。如果有相关房间,这个方法会将当前玩家直接加入房间,如果在房间不存在,则创建一个新房间。
try {
// 例如,有 4 个玩家同时加入一个房间名称为 「room1」 的房间,如果不存在,则创建并加入
await client.JoinOrCreateRoom("room1");
} catch (PlayException e) {
// 加入房间失败,也没有成功创建房间
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
更多关于 JoinOrCreateRoom,请参考 API 文档。
新玩家加入事件
对于已经在房间的玩家,当有新玩家加入到房间时,服务端会派发 OnPlayerRoomJoined(新玩家加入)事件通知客户端,客户端可以通过新玩家的属性,做一些显示逻辑。
// 注册新玩家加入事件
client.OnPlayerRoomJoined += newPlayer => {
// TODO 新玩家加入逻辑
};
可以通过 client.Room.PlayerList 获取房间内的所有玩家。
判断本地玩家
拿到房间内的所有玩家后,可以判断某一个 Player 是否是当前本地玩家。
var players = client.Room.PlayerList;
var player = players[0];
var isLocal = player.isLocal;
离开房间
当玩家想要「主动」离开房间时,可以调用下面的接口。
try {
await client.LeaveRoom();
// 离开房间成功,可以执行跳转场景等逻辑
} catch (PlayException e) {
// 离开房间失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
房间里的其他玩家将会接收到 OnPlayerRoomLeft(有玩家离开房间)事件。
// 注册有玩家离开房间事件
client.OnPlayerRoomLeft += leftPlayer => {
// TODO 可以执行玩家离开的销毁工作
}
相关事件
| 事件 | 参数 | 描述 |
|---|---|---|
| OnPlayerRoomJoined | newPlayer | 新玩家加入房间 |
| OnPlayerRoomLeft | leftPlayer | 玩家离开房间 |
MasterClient
多人对战服务默认房间的创建者房主为 MasterClient。在游戏中他可以关闭房间、设置房间不可见、踢人等,同时他所在的设备还承担了「 逻辑运算功能」,例如它在游戏中可以负责以下场景:
- 在卡牌游戏中为用户发牌;
- 控制刷怪的时机及逻辑;
- 游戏结束时判断胜负;
- …等等。
MasterClient 位于玩家客户端
您可以将 MasterClient 的逻辑写在客户端中,作为房间创建者的玩家终端会承担游戏运算。在这种情况下,多人对战为 MasterClient 提供了以下方便的功能:
MasterClient 掉线转移
当 MasterClient 掉线后,多人对战服务会指任一名新的玩家客户端为 MasterClient。即使当原 MasterClient 恢复上线之后,也不会成为新的 MasterClient。当 MasterClient 变化时,SDK 会派发 OnMasterSwitched(主机切换)事件通知客户端。
// 注册主机切换事件
client.OnMasterSwitched += newMaster => {
// TODO 可以做主机切换的展示
// 可以根据判断当前客户端是否是 Master,来确定是否执行逻辑处理。
if (client.Player.IsMaster) {
}
}
相关事件
| 事件 | 参数 | 描述 |
|---|---|---|
| OnMasterSwitched | newMaster | Master 更换 |
指定其他成员为 MasterClient
在游戏过程中,MasterClient 如果不想再承担运算功能,可以调用以下方法主动将自己的角色转移给其他玩家客户端:
try {
// 通过玩家的 Id 指定 MasterClient
await client.SetMaster(newMasterId);
} catch (PlayException e) {
// 设置 Master 失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
转移 MasterClient 身份后,房间内所有玩家都会收到 OnMasterSwitched 事件。
MasterClient 位于服务端
为了确保游戏安全,防止用户作弊等,我们推荐您将 MasterClient 托管在 TDS 提供的服务端 Client Engine 中,同时做以下权限配置:
MasterClient 掉线不转移
将 MasterClient 放在服务端后,MasterClient 意外掉线时也不应当转移角色,此时需 要在创建房间时指定 FixedMaster Flag:
var options = new RoomOptions {
Flag = CreateRoomFlag.FixedMaster
};
await client.CreateRoom(roomOptions: options);
MasterClient 的操作
设置房间是否开放
MasterClient 可以设置房间是否开放,当房间关闭时,不允许其他玩家加入。
try {
// 设置房间关闭
await client.SetRoomOpen(false);
Debug.Log(client.Room.Open);
} catch (PlayException e) {
// 设置失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
设置房间是否可见
MasterClient 可以设置房间是否可见,当房间不可见时,这个房间将不会出现在玩家的大厅房间列表中,但是其他玩家可以通过指定 roomName 加入。
try {
// 设置房间不可见
await client.SetRoomVisible(false);
Debug.Log(client.Room.Visible);
} catch (PlayException e) {
// 设置失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
踢人
MasterClient 可以把房间内的其他玩家踢出房间:
try {
// 可以传入踢人的 code 和 msg
await client.KickPlayer(otherPlayer.ActorId, 1, "你已被踢出房间");
} catch (PlayException e) {
// 踢人失败
Debug.LogErrorFormat("{0}, {1}", e.Code, e.Detail);
}
踢出房间后,被踢的玩家会收到 OnRoomKicked 事件。
client.OnRoomKicked += (code, msg) => {
// code 和 msg 就是 MasterClient 在踢人时传递的信息
};
同时除 MasterClient 之外,其他还存在房间的玩家会收到 OnPlayerRoomLeft 事件:
client.OnPlayerRoomLeft += leftPlayer => {
};
自定义属性及同步
为了满足开发者不同的游戏需求,多人对战 SDK 允许开发者设置「自定义属性」。
自定义属性同步的主要作用包括:
- 使每个客户端的数据保持一致。
- 自定义属性由服务端管理,当有玩家进入房间后,即可得到所有的自定义属性。
自定义属性又分为「房间自定义属性」和「玩家自定义属性」。