多人在线对战服务概述
多人在线对战是LeanCloud专门针对多人在线游戏推出的后端服务。 开发者无需搭建自己的后端系统,利用云服务即可轻松实现游戏内玩家匹配、在线战斗消息同步等功能。
核心功能特性 核心概念 Client 和 UserId
多人在线对战服务中的每个终端称为“客户端”。 每个Client在整个对战服务中都有一个唯一的标识符UserId。 该UserId只允许英文、数字和下划线,长度不能超过32个字符。 在一个全球唯一的应用程序内。 每个游戏玩家肯定都是一个Client,但并不是所有的Client都是真正的玩家,比如托管在Client Engine中管理房间的MasterClient或者自己编写的AI玩家。
多人在线对战服务仅允许一个客户端同时与服务器建立连接。 如果您已经使用该UserId登录并尝试再次登录,则第二次登录将踢出上一次登录。
房间和演员 ID
当玩家匹配成功后,就会进入同一个房间进行游戏,战斗信息会在这个房间内快速同步。 每个玩家在房间里都有自己专属的ActorId,房间里的所有通信都是通过ActorId来传递的。 玩家退出房间后,ActorId失效。 当玩家进入下一个房间时多人在线游戏引擎,他会获得该房间内一个新的ActorId。
一个房间最多支持10人同时在线。
游戏核心流程
下面是一个简单的示例代码,可以帮助您更快地理解整个流程。 详细开发指南请参考:
连接到服务器
const client = new Client({
// 设置 APP ID
appId: {{appid}},
// 设置 APP Key
appKey: {{appkey}},
// 设置 Server (请将 xxx.example.com 替换为你的应用绑定的自定义 API 域名)
playServer: 'https://xxx.example.com',
// 设置用户 id
userId: 'leancloud'
// 设置游戏版本号,选填,默认 0.0.1,不同版本的玩家不会匹配到同一个房间
gameVersion: '0.0.1'
});
client.connect().then(()=> {
// 连接成功
}).catch(console.error);
Play.UserID = "Mario";
// 连接服务器时可以声明游戏版本,不同版本的玩家不会匹配到同一个房间
Play.Connect("0.0.1");
玩家是随机匹配的
单人游戏时,最常见的场景是随机匹配其他玩家快速启动。 具体实施步骤如下:
1. 调用JoinRandomRoom开始匹配。
client.joinRandomRoom().then(() => {
// 成功加入房间
}).catch(console.error);
Play.JoinRandomRoom();
2.如果一切顺利,您将进入一个有空位的房间并开始游戏。
// JavaScript SDK 通过 joinRandomRoom 的 Promise 判断是否加入房间成功
play.On(Event.ROOM_JOINED, (evtData) => {
// 成功加入房间
});
3. 如果没有空房间,则加入失败。 此时像素游戏素材,在失败触发的回调中会创建一个房间,并等待其他人加入。 创建房间时:
client.joinRandomRoom().then().catch((error) => {
if (error.code === 4301) {
const options = {
// 设置最大人数,当房间满员时,服务端不会再匹配新的玩家进来。
maxPlayerCount: 4,
// 设置玩家掉线后的保留时间为 120 秒
playerTtl: 120,
};
// 创建房间
client.createRoom({
roomOptions: options
}).then(()=> {
// 创建房间成功
});
}
});
// 加入失败时,这个回调会被触发
play.On(Event.ROOM_JOIN_FAILED, (evtData) =>
{
var options = new RoomOptions()
{
// 设置最大人数,当房间满员时,服务端不会再匹配新的玩家进来。
MaxPlayerCount = 4,
// 设置玩家掉线后的保留时间为 120 秒
PlayerTtl = 120,
};
play.CreateRoom(roomOptions: options);
});
自定义房间匹配规则
有时我们希望将技术水平相似的玩家匹配在一起。 例如当前玩家等级为5级,则只能匹配0-10级的玩家,无法匹配10级以上的玩家。 这个场景可以通过设置房间的属性来实现。 具体实现逻辑如下:
1.确定匹配属性。 例如,0-10级为1级,10级以上为2级。
var matchLevel = 0;
if (level < 10) {
matchLevel = 1;
} else
matchLevel = 2;
}
int matchLevel = 0;
if (level < 10) {
matchLevel = 1;
} else
matchLevel = 2;
}
2.根据匹配属性加入房间
const matchProps = {
level: matchLevel,
};
client.joinRandomRoom({matchProperties: matchProps}).then(() => {
// 成功加入房间
}).catch(console.error);
Hashtable matchProp = new Hashtable();
matchProp.Add("matchLevel", matchLevel);
Play.JoinRandomRoom(matchProp);
3.如果随机加入房间失败,请创建属性匹配的房间,等待其他同等级的人加入。
const matchProps = {
level: matchLevel,
};
client.joinRandomRoom({matchProperties: matchProps}).then().catch((error) => {
if (error.code === 4301) {
const options = {
// 设置最大人数,当房间满员时,服务端不会再匹配新的玩家进来。
maxPlayerCount: 4,
// 设置玩家掉线后的保留时间为 120 秒
playerTtl: 120,
// 房间的自定义属性
customRoomProperties: matchProps,
// 从房间的自定义属性中选择匹配用的 key
customRoomPropertyKeysForLobby: ['level'],
}
client.createRoom({
roomOptions: options
}).then().catch(console.error);
}
});
play.On(Event.ROOM_JOIN_FAILED, (error) => {
if (error["code"] == 4301)
{
var props = new Dictionary();
props.Add("level", 2);
var options = new RoomOptions()
{
// 设置最大人数,当房间满员时,服务端不会再匹配新的玩家进来。
MaxPlayerCount = 3,
// 设置玩家掉线后的保留时间为 120 秒
PlayerTtl = 120,
// 房间的自定义属性
CustomRoomProperties = props,
// 从房间的自定义属性中选择匹配用的 key
CustoRoomPropertyKeysForLobby = new List() { "level" },
};
play.CreateRoom(roomOptions: options);
}
});
与朋友一起玩
假设玩家 A 想和他的同性恋朋友玩家 B 玩游戏。 这种情况下,有两种情况:
陌生人不得加入
1、玩家A创建一个房间,并将该房间设置为不可见人物立绘,这样其他人就不会随机匹配到玩家A创建的房间。
const options = {
// 房间不可见
visible: false,
};
client.createRoom({
roomOptions: options,
}).then().catch(console.error);
var options = new RoomOptions()
{
Visible = false,
};
play.CreateRoom(roomOptions: options);
2.玩家A通过某种通讯方式(如LeanCloud即时通讯)告诉玩家B房间名称。
3.玩家B根据房间名称加入房间。
client.joinRoom('LiLeiRoom').then().catch(console.error);
Play.JoinRoom(roomName);
朋友和陌生人一起玩
PlayerA通过某种通信方式(例如LeanCloud即时通讯)邀请PlayerB,PlayerB接受邀请。
1.玩家A与玩家B建立比赛进入某个房间。
client.joinRandomRoom({expectedUserIds: ["playerB"]}).then(() => {
// 加入成功
}).catch(console.error);
Play.JoinRandomRoom(expectedUserIds: new string[] {"playerB"});
2. 如果有足够的空房间,则玩家A加入成功。
// JavaScript SDK 通过 joinRandomRoom 的 Promise 判断是否加入房间成功
play.On(Event.ROOM_JOINED, (evtData) => {
// TODO 可以做跳转场景之类的操作
});
PlayerA通过某种通讯方式(如LeanCloud即时通讯)告诉PlayerB自己加入的房间的roomName,PlayerB根据roomName加入房间。
client.joinRoom('LiLeiRoom').then().catch(console.error);
Play.JoinRoom(roomName);
3、如果没有合适的房间,创建并加入房间:
const expectedUserIds = ['playerB'];
client.joinRandomRoom({expectedUserIds}).then().catch((error) => {
// 没有空房间或房间位置不够
if (error.code === 4301 || error.code === 4302) {
client.createRoom({
expectedUserIds: expectedUserIds
}).then().catch(console.error);
}
});
play.On(Event.ROOM_JOIN_FAILED, (error) => {
var expectedUserIds = new List() { "cr3_2" };
Play.CreateRoom(expectedUserIds: expectedUserIds);
});
PlayerA创建房间后,通过某种通信方式(如LeanCloud即时通讯)告诉PlayerB自己加入的房间的roomName,PlayerB根据roomName加入房间。
client.joinRoom('LiLeiRoom').then().catch(console.error);
Play.JoinRoom(roomName);
更多匹配接口请参考房间匹配文档:,。
游戏中的相关概念 开始游戏
在比赛开始之前,我们建议每个玩家都有一个准备状态。 当所有玩家都准备好后,MasterClient 开始游戏。 开始游戏前需要将房间设置为隐身,以防止游戏过程中其他玩家匹配到。
玩家A通过设置自定义属性来设置就绪状态:
// 玩家设置准备状态
const props = {
ready: true,
};
// 请求设置玩家属性
play.player.setCustomProperties(props).then(() => {
// 设置属性成功
}).catch(console.error);
// 玩家设置准备状态
Hashtable prop = new Hashtable();
prop.Add("ready", true);
play.Player.SetCustomProperties(props);
所有玩家(包括玩家A)都会收到事件回调通知:
play.on(Event.PLAYER_CUSTOM_PROPERTIES_CHANGED, (data) => {
// MasterClient 才会执行这个运算
if (play.player.isMaster) {
// 在自己写的方法中检查已经准备的玩家数量,可以通过 play.room.playerList 获取玩家列表。
const readyPlayerCount = getReadyPlayerCount();
// 如果都准备好了就开始游戏
if (readyPlayersCount > 1 && readyPlayersCount == play.room.playerList.length())
{
// 设置房间不可见,避免其他玩家被匹配进来
play.setRoomVisible(false);
// 开始游戏
start();
}
}
});
play.On(Event.PLAYER_CUSTOM_PROPERTIES_CHANGED, (evtData) => {
// MasterClient 才会执行这个运算
if (play.Player.IsMaster)
{
// 在自己写的方法中检查已经准备的玩家数量,可以通过 play.Room.playerList 获取玩家列表。
var readyPlayerCount = getReadyPlayerCount();
// 如果都准备好了就开始游戏
if (readyPlayersCount > 1 && readyPlayersCount == Play.Players.Count())
{
// 设置房间不可见,避免其他玩家被匹配进来
play.SetRoomVisible(false);
// 开始游戏
start();
}
}
});
在游戏中发送消息
游戏中的大部分消息都会发送给MasterClient多人在线游戏引擎,然后由MasterClient计算后决定下一步的动作。 假设有这样一个场景:玩家A完成跟进后,告诉MasterClient跟进完成。 MasterClient收到消息后,通知大家当前需要玩家B进行下一步操作。
具体消息发送流程如下:
1. 玩家A发送自定义事件follow来通知MasterClient已经完成follow。
// 设置事件的接收组为 Master
const options = {
receiverGroup: ReceiverGroup.MasterClient,
};
// 设置要发送的信息
const eventData = {
actorId: play.player.actorId,
};
// 设置事件 Id
const FOLLOW_EVENT_ID = 1;
// 发送事件
play.sendEvent(FOLLOW_EVENT_ID, eventData, options);
// 设置事件的接收组为 Master
var options = new SendEventOptions() {
ReceiverGroup = ReceiverGroup.MasterClient
};
// 设置要发送的信息
var eventData = new Dictionary();
eventData.Add("actorId", play.player.actorId);
// 设置事件 Id
byte followEventId = 1;
// 发送事件
play.SendEvent(followEventId, eventData, options);
2.会触发MasterClient中的相关方法。 MasterClient计算出下一个要操作的玩家是PlayerB,然后调用next方法通知所有玩家PlayerB当前需要操作。
// Event.CUSTOM_EVENT 方法会被触发
play.on(Event.CUSTOM_EVENT, event => {
const { eventId } = event;
if (eventId === FOLLOW_EVENT_ID) {
// follow 自定义事件
// 判断下一步需要 PlayerB 操作
int PlayerBId = getNextPlayerId();
// 通知所有玩家下一步需要 PlayerB 操作。
const options = {
receiverGroup: ReceiverGroup.All,
};
const eventData = {
actorId: PlayerBId,
};
const NEXT_EVENT_ID = 2;
play.sendEvent(NEXT_EVENT_ID, eventData, options);
}
});
// Event.CUSTOM_EVENT 方法会被触发
play.On(Event.CUSTOM_EVENT, (evtData) => {
// 获取事件参数
var eventId = evtData["eventId"];
if (eventId == followEventId) {
byte nextEventId = 2;
// 事件内容
var eventData = new Dictionary();
eventData.Add("actorId", PlayerBId);
// 发送给所有人
var options = new SendEventOptions()
{
ReceiverGroup = ReceiverGroup.All
};
play.SendEvent(nextEventId, eventData, options);
}
});
3、触发所有玩家相关方法。
// Event.CUSTOM_EVENT 方法会被触发
play.on(Event.CUSTOM_EVENT, event => {
const { eventId, eventData } = event;
if (eventId === FOLLOW_EVENT_ID) {
......
};
if (eventId === NEXT_EVENT_ID) {
// next 事件逻辑
console.log('Next Player:' + eventData.actorId);
}
});
play.On(Event.CUSTOM_EVENT, (evtData) => {
if (eventId == followEventId)
{
......
}
if (eventId == nextEventId)
{
// next 事件逻辑
var actorId = evtData["actorId"];
}
});
更详细的使用和介绍请参考:
游戏断线重连
如果MasterClient位于客户端,当MasterClient断开连接后,多人对战服务会重新选择其他成员成为新的MasterClient。 原MasterClient返回房间后将成为普通会员。 详情请参阅。
退出房间
play.leaveRoom().then(() => {
// 成功退出房间
}).catch(console.error);
Play.LeaveRoom();
文档JavaScriptC#Demo价格
多人在线对战的核心计费单位是CCU,即同时在线的人数。 价格请参考官方网站。
在线聊天
为了及时解答您的疑问,进一步了解游戏开发者的需求和使用场景,我们成立了游戏开发群,欢迎各位游戏开发者加入。 详情>>>