匹配同玩
收藏
我的收藏本文对于玩法开启和接入了云同步能力后,如何接入使用匹配同玩相关接口功能,进行说明。
流程总览:
- 1.用户A 和 B 分别将玩法云启动
- 2.都进入单播状态
- 3.A 和 B 调用 匹配
- a.匹配成功,返回匹配信息会决定其中谁做房主、谁做加入的玩家。
- 4.A与B进入同玩状态
- a.其中 A 做房主。
- b.其中 B 作为玩家,系统会将其做切流处理,加入到 A 的云游戏实例。
- c.进行同玩游戏
- 5.游戏结束后,调用 结束同玩
- a.B 结束同玩,从A的实例断开退出。回流,返回到自己实例单播状态。
- b.A 结束了同玩状态,回到单播状态。
- 6.回到单播状态
接口总览:
// 匹配同玩接口: 访问接口入口 MatchManager = ICloudSync.Instance.MatchManager; 请求匹配 RequestMatch 结束同玩 EndMatchGame // 监听事件:玩家加入/离开 ICloudSync.Instance.OnSeatPlayerJoined ICloudSync.Instance.OnSeatPlayerLeaving // 监听事件:匹配到了其他用户 MatchManager.OnMatchUsers // 监听事件:结束同玩了(返回单播) MatchManager.OnEndMatchGame
匹配同玩
流程图:
单播状态 → 匹配同玩 流程图:
- 1.前置:云同步初始化
- 2.添加事件监听
- 3.调用请求匹配
RequestMatch
- a.触发事件
OnMatchUsers
匹配到了用户- i.A收到匹配信息:自己做房主,匹配到了对方玩家B
- ii.B收到匹配信息:匹配到了对方玩家A 做房主,自己做玩家。
- b.[可选]:取消匹配
- c.如果匹配超时、匹配失败,返回匹配结果:
IMatchResult
- 4.切流,进入同玩(系统自动完成)
- a.其中,房主A
BeginHost
开始房主状态- b.其中,其他玩家B
SwitchTo
切流到房主A的实例- 5.
RequestMatch
返回匹配结果:IMatchResult
- 6.触发事件
OnSeatPlayerJoined
玩家加入了(玩家B)- 7.A与B进入同玩状态,进行同玩游戏
1.前置初始化
示例代码:
private async Task<bool> InitCloudGame(string appId) { // 云同步初始化 Init var initResult = await ICloudSync.Instance.Init(appId, new UDeviceFactory(viewProvider)); if (!initResult.IsSuccess) { Debug.LogError($"Init 初始化失败,无法启动云游戏 {initResult.Message}"); return false; } return true; }
2.添加事件监听
匹配同玩相关事件,有以下几个:
- 1.必要:玩家加入、离开
- 2.可选:匹配到了用户
- 3.必要:结束同玩了
示例代码:
// 添加事件监听 private void InitEvents() { // 监听事件:玩家加入/离开 ICloudSync.Instance.OnSeatPlayerJoined += OnPlayerJoined; ICloudSync.Instance.OnSeatPlayerLeaving += OnPlayerLeaving; MatchManager = ICloudSync.Instance.MatchManager; // 可选:监听事件:匹配到了其他用户 MatchManager.OnMatchUsers += OnMatchUsers; // 监听事件:结束同玩了(返回单播) MatchManager.OnEndMatchGame += OnEndMatchGame; }
3.请求匹配 RequestMatch
请求匹配,使用系统自带提供的匹配池。 成功后进入多人同玩状态。
示例代码:1. 简单版匹配:
/// 开始匹配 private async void StartMatch() { MatchManager = ICloudSync.Instance.MatchManager; // 匹配配置,选择一个系统自带提供的匹配池 var matchConfig = new SimpleMatchConfig() { PoolType = SimpleMatchPoolType.P1v1, // 2人 1v1 MatchTag = "Demo-test-1.0" // 匹配标签,区分开用户 }; // 开始匹配,传入匹配配置。 成功后进入系统自动进入多人同玩状态。 var task = MatchManager.RequestMatch(matchConfig); var result = await task; // 等待结果 // todo: 处理结果:OnMatchResult(result); }
匹配配置
SimpleMatchConfig
的字段含义说明:public class SimpleMatchConfig { /// 选择匹配池,对应一套匹配规则 public SimpleMatchPoolType PoolType; /// 匹配标签,区分开用户。 建议用不同值区分开测试环境"test"、线上环境"online",也可以用于分开不同版本"1.0","2.0"。 Example: 例如:"test-1.0", "online-2.0" public string MatchTag; }
- ◦其中,
PoolType
匹配池,提供以下几种:public enum SimpleMatchPoolType { /// 2人 1v1 P1v1, /// 4人 2v2 P2v2, /// 4人各自1队 P1x4, /// 3人各自1队 P1x3, }
- ◦其中,
MatchTag
匹配标签:- ▪开发者可自定义。不同标签的用户会被区分开,不会匹配到一起。
- ▪建议:用不同值区分开测试环境"test"、线上环境"online",也可以用于分开不同版本"1.0","2.0"。
- ▪Example:
MatchTag
例如:"test-1.0"
, "online-2.0"
[可选]:示例代码:2. 匹配、支持中途取消匹配:
private CancellationTokenSource _matchCancel; /// 开始匹配 private async void StartMatch() { // 设置本地游戏状态,可自定义表现,例如:显示“匹配中”文字、Loading转圈、按钮置灰等等... SetState(State.Matching); // 创建取消用的 CancellationTokenSource,using使其自动Dispose using CancellationTokenSource cancel = _matchCancel = new CancellationTokenSource(); // 匹配配置,选择一个系统自带提供的匹配池 var matchConfig = ...; // 开始匹配,传入匹配配置,传入可取消用的cancel.Token。 成功后进入系统自动进入多人同玩状态。 var task = MatchManager.RequestMatch(matchConfig, cancel.Token); var result = await task; // 等待结果 _matchCancel = null; // 已出结果,不再需要取消,应把 TokenSource 引用置null // todo: 处理结果:OnMatchResult(result); } /// 主动取消匹配 private void CancelMatch() { if (_matchCancel != null) { // 主动取消。 若成功取消,`RequestMatch`返回结果的`IMatchResult.Code`会是`Cancelled` _matchCancel.Cancel(); // 取消后,应把 TokenSource 引用置null _matchCancel = null; } }
4.事件:匹配到了用户
收到了匹配到的用户是谁的信息。可以做相关展示、信息提示。相应要做什么表现,这部分开发者可以自定义实现。
示例代码:
// 监听事件 MatchManager.OnMatchUsers += OnMatchUsers; private void OnMatchUsers(IMatchResult matchResult) { var isHost = matchResult.IsHost; var myIndex = matchResult.MyIndex; var users = matchResult.AllUsers; var opponents = users.Where(s => s.RoomIndex != myIndex).ToList(); var oppoNames = string.Join(", ", opponents.Select(s => s.Nickname).ToList()); ShowHostNote($"匹配到了用户: {oppoNames}"); }
5.处理匹配结果:IMatchResult
请求匹配的接口
MatchManager.RequestMatch
返回类型为 Task<IMatchResult>
。在
result = await task
等待到结果后,应进行处理。无论成功、失败、取消,都会返回结果。
- 1.若失败
!result.IsSuccess
,那么包含多个情况,应根据返回码:result.Code
枚举判断,枚举MatchResultCode
类型定义如下:- a.成功:Success
- b.取消了:Cancelled
- c.超时(即:超过了一定时间,没有匹配到):Timeout
- d.错误:Error
- 2.若成功,应当按
result.IsHost
是否为true,区分我方是否房主:- a.A: IsHost true 若我方是房主,应负责加载启动本局游戏逻辑(包括例如加载局内游戏场景、界面UI等)。
- b.B: IsHost false 若我方是要加入同玩的玩家,系统会切流加入到房主A的实例去同玩。 本地无需特殊处理,可以自定义显示,例如:显示“与...同玩中”、显示Loading。
- c.从
result
中,可以获取到匹配结果信息内容,例如:标识一场匹配的id`MatchId`,我的座位号`MyIndex`,房主用户`HostUser`;各个玩家用户的列表`AllUsers`,Team列表`Teams`
示例代码:处理匹配结果、处理失败情况:
/// 处理匹配结果 private void OnMatchResult(IMatchResult result) { _matchResult = result; // 处理失败情况 if (!result.IsSuccess) { var debugMsg = $"code: {result.Code} ({(int)result.Code}), msg: {result.Message}"; // 根据返回码:`result.Code`枚举判断 switch (result.Code) { case MatchResultCode.Success: break; case MatchResultCode.Cancelled: Debug.Log($"StartMatch 取消 {debugMsg}"); break; case MatchResultCode.Timeout: Debug.LogWarning($"StartMatch 匹配超时 {debugMsg}"); break; case MatchResultCode.Error: default: Debug.LogError($"StartMatch 匹配失败 {debugMsg}"); break; } // 重置本地游戏状态、界面,并返回,允许后续操作。 SetState(State.Ready); return; } var isHost = result.IsHost; Debug.Log($"StartMatch 匹配成功 isHost: {isHost}"); // 处理成功情况 OnMatchSuccess(isHost); }
示例代码:处理匹配结果成功情况:
/// 处理匹配结果成功情况 private void OnMatchSuccess(bool isHost) { var result = _matchResult; if (isHost) { // 匹配成功后,若我方是房主,应负责加载启动本局游戏(包括例如加载游戏资源、切换场景、显示界面UI等)。 // 房主A SetState(State.InGameHost); // 从result中,可以获取到匹配结果信息内容,例如:标识一场匹配的id`MatchId`,我的座位号`MyIndex`,房主用户`HostUser`;各个玩家用户的列表`AllUsers`,Team列表`Teams` var playersCount = result.AllUsers.Count; game.LoadGame(new GameInfo { isPk = true, playersCount = playersCount, }); } else { // 若我方是要加入同玩的玩家,系统会切流加入到房主A的实例去同玩。 本地无需特殊处理,可以自定义显示,例如:显示“与...同玩中”、显示Loading。 // 玩家B SetState(State.InConnectOther); } }
6.事件:玩家加入了
注意,房主需要特别关注其他玩家加入、离开的事件。
- •原因:在”房主A“这一侧,匹配成功后,而对方”玩家B“可能由于网络延迟、操作延迟等,时序上并不是完全相同时刻就加入进来,通常会稍晚一点。
- •⚠ 建议通常应避免:房主自己收到匹配成功的结果后,还没有等待其他玩家的加入,自己就开始了游戏内容、走后续玩法逻辑了。
- •💡建议通常做法:
匹配成功后,若我方是房主:
- 1.前置:监听事件:玩家加入/离开
- 2.先加载游戏资源、切换场景、显示界面UI等。
- 3.等待和检查后,才开局。 具体包括:
- a.应当等待和检查玩家B的加入事件
- b.应当检查玩家加入到齐、确认OK后,开始正式一局游戏玩法
- c.[可选]:若玩家B还未加入,可以显示例如”等待其他玩家...“
- d.[可选]:若玩家一直不加入、或加入后又退出,可以做自定义的处理,例如:可进行弹框提示,可提供给用户界面按钮,可以提前结束本局同玩。
示例代码:
// 监听事件:玩家加入/离开 ICloudSync.Instance.OnSeatPlayerJoined += OnPlayerJoined; ICloudSync.Instance.OnSeatPlayerLeaving += OnPlayerLeaving; // 匹配成功 private void OnMatchSuccess(bool isHost) { if (isHost) { // 匹配成功后,若我方是房主,应负责加载启动本局游戏(包括例如加载游戏资源、切换场景、显示界面UI等) LoadGame(...); } } // 等待和检查玩家B的加入事件 private void OnPlayerJoined(ICloudSeat seat) { Debug.Log($"Game OnPlayerJoin seat index: {seat.Index}"); if (State == GameState.Loaded) CheckStartGame(); } // 加载游戏 public void LoadGame(...) { State = GameState.Loaded; // todo: 自定义实现:加载游戏资源、切换场景、显示界面UI等... // 检查确认 CheckStartGame(); } // 检查确认 private void CheckStartGame() { if (State != GameState.Loaded) return; // 检查玩家加入到齐、确认OK后,开始正式一局游戏玩法。 if (ArePlayersJoined(seats)) { InitPlayerInfos(seats); // 开始正式一局游戏玩法 StartGame(); } }
结束同玩
流程图:
同玩中 → 结束同玩流程图:
- 1.调用结束同玩
EndMatchGame
- a.默认:所有人结束同玩
- b.可选:
EndMatchGame(SeatIndex)
踢掉单个,指定让单个用户退出 - 2.触发事件:
OnEndMatchGame
结束同玩了(返回单播)- 3.触发事件:
OnSeatPlayerLeaving
玩家 B 离开
结束同玩 EndMatchGame
结束同玩:让所有人、或让指定座位号的玩家,回到单播状态。
接口方法
ICloudMatchManager.EndMatchGame
支持有多种重载:EndMatchGame(string endInfo = "") | 结束同玩:所有人回到单播状态。 |
EndMatchGame(InfoMapping infoMapping) | 结束同玩:所有人回到单播状态。可以给每个用户分别发送不同的结束信息。 |
EndMatchGame(SeatIndex roomIndex, string endInfo = "") | 指定座位号,单个用户结束同玩:使他退出、回到单播状态。 |
- •参数 endInfo :结束信息,用json透传,传递给同玩的用户
- •参数 infoMapping:结束信息映射,可以给每个用户分别发送不同的结束信息
- •参数 SeatIndex:可以指定座位号,单个用户结束同玩(使他退出、回到单播状态)
使用举例:当 A 是房主,B,C,D 是加入的同玩玩家:
- •同玩一局游戏结束,显示结算界面,本局分数情况。 界面中显示按钮【退出】
- •当点击【退出】时,应区分是否房主,选择如何结束同玩:
- •若是房主A点击【退出】,调用
EndMatchGame
结束所有人同玩(不指定座位号),并传入结束信息- ◦玩家 B,C,D 会退出同玩、回流,回到自己的单播状态。
- ◦玩家 B,C,D 原实例中,会收到
OnEndMatchGame
事件- ◦房主A(在上述操作完成后)也会收到
OnEndMatchGame
事件,进入单播状态。- ◦注:相关说明:
- ▪调用
EndMatchGame
,是在A实例的玩法进程中调用- ▪B,C,D 会断开从A实例的拉流。 回流后的单播状态:单播自己的画面、不再从A拉流画面。
- ▪B,C,D 收到
OnEndMatchGame
事件,会收到当时调用结束同玩的参数 endInfo
(或infoMapping
)携带发送来的结束信息- ▪注意:B,C,D 收到
OnEndMatchGame
事件,是在 B,C,D 自己原来实例的玩法进程中,而不是在刚才A的进程中收到。- •若是玩家B点击【退出】,调用
EndMatchGame(SeatIndex)
且指定座位号 index1,[可选] 自定义结束信息- ◦所指定的玩家 B 会退出同玩、回流,回到自己的单播状态。
- ◦玩家 B 原实例中,会收到
OnEndMatchGame
事件- ◦注:相关说明:
- ▪调用指定座位号的
EndMatchGame
,也是在A实例的玩法进程中调用。只不过一般是由玩家B 的 操作点击触发的,但执行的代码还是在A这里。- ▪座位号 SeatIndex 序号从 0 开始,0 为房主自己,从 1 开始为其他同玩玩家。
- ▪[可选] 自定义结束信息,因为他是自己主动退了, 他已经看到了结算信息,所以可按需要自定义透传什么结束信息。
- ▪注意:同上结束所有人同玩的情况一样,B 会收到
OnEndMatchGame
事件,是在自己原来实例的玩法进程中。- •如果其他玩家都退出了,A还没有点击【退出】:
- ◦注:其他玩家退出同玩时,房主侧不强制退出同玩状态。开发者可以自己调用`结束同玩`的接口(不指定座位号)。
示例代码:结束同玩:
private ICloudSeat _seat; /// 显示结算 public void ShowGameResult(GameResult result, ICloudSeat seat) { _seat = view.Seat; } // 当点击【退出】 private async void OnClickExit() { await EndMatchGame(_seat.IsHost(), _seat.Index); // todo: 结束同玩了,后续流程 OnConfirmEnd(); } // 区分是否房主,结束同玩 private async Task EndMatchGame(bool isHost, SeatIndex roomIndex) { var resultJson = JsonUtility.ToJson(_result); Debug.Log($"GameResult EndMatchGame isHost: {isHost}, roomIndex: {roomIndex}"); if (isHost) { // 若是房主A点击【退出】,调用EndMatchGame 且不指定座位号,并传入结束信息 // 逻辑其实是踢所有人,这时我们需要把结算结果透传给另一个主播 // 让另一个主播在回到原实例后仍可继续看结算结果(收到OnEndMatchGame事件) await ICloudSync.Instance.MatchManager.EndMatchGame(resultJson); } else { // 若是玩家B点击【退出】,调用EndMatchGame 且指定座位号 index1,并传入结束信息 // [可选] 自定义结束信息,因为他是自己主动退了, 他已经看到了结算信息,所以可按需要自定义透传什么结束信息。 await ICloudSync.Instance.MatchManager.EndMatchGame(roomIndex, resultJson); } }
事件:结束同玩了
调用结束同玩
ICloudMatchManager.EndMatchGame
后:- ◦加入了的玩家,回流到自己实例后,会收到事件:
OnEndMatchGame
。- ◦如果房主结束同玩,相关操作结束后,房主自己也会收到事件:
OnEndMatchGame
。
使用举例,详见上一小节”结束同玩 EndMatchGame“ 中的使用举例。已详细说明。
注:房主结束同玩后,以及其他玩家回流、返回自己实例单播后,分别要做什么表现,这部分开发者可以自定义实现。
示例代码:
// 监听事件 MatchManager.OnEndMatchGame += OnEndMatchGame; // 收到事件:结束同玩了(返回单播) private void OnEndMatchGame(string info) { var hasEndInfo = !string.IsNullOrEmpty(info); if (hasEndInfo) { // 玩家回流到自己实例 ShowHostNote("回流了"); try { // 回流的玩家,收到结算信息 var gameResult = JsonUtility.FromJson<GameResult>(info); if (gameResult != null) ViewA.ShowGameResult(gameResult, false); } catch (Exception) { ShowHostAlert("endInfo", info); } } else { // 房主自己结束同玩了(或者调用结束同玩时,没有传入结束信息、为空) ShowHostNote("结束同玩了"); } SetState(State.Ready); }
事件:玩家离开
如果同玩状态下,玩家离开,可以做相应展示。
如果离开后,没有其他玩家了,要做什么表现,这部分开发者可以自定义实现。
举例:如果同玩已正式开始一局,可以将本局提前结束。
示例代码:
private void OnPlayerLeaving(ICloudSeat seat) { var playerText = seat.IsHost() ? "主播" : $"玩家{seat.IntIndex + 1}"; ViewA.ShowNote($"{playerText}: {seat.PlayerInfo?.NickName} 离开了游戏"); if (State != GameState.Started) return; CheckGameEnd(); } private void CheckGameEnd() { if (State != GameState.Started) return; if (AreOtherSeatsDisconnected()) { // 其他玩家都已退出同玩。房主侧不强制退出,开发者可以在做好合适的展示后,自己调用`结束同玩`接口 Debug.Log("其他玩家都已退出同玩"); EndGame(); return; } if (_liveBlockCount == 0) { Debug.Log("游戏关卡完成"); EndGame(); } }