互联网游戏开发秘籍:与全球玩家匹配,畅享多人在线对战

互联网游戏开发秘籍:与全球玩家匹配,畅享多人在线对战

多人在线对战服务概述

多人在线对战是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,即同时在线的人数。 价格请参考官方网站。

在线聊天

为了及时解答您的疑问,进一步了解游戏开发者的需求和使用场景,我们成立了游戏开发群,欢迎各位游戏开发者加入。 详情>>>

文章来源:https://leancloud.cn/docs/multiplayer.html