房间
1. 概述
房间服务与DS托管和管理服务配合使用,为游戏提供了一种聚集玩家开始战斗的方式。玩家可以创建私人房间与特定玩家一起游戏,也可以创建任何人都可以加入的公开房间。房间服务还提供房间管理、搜索、文字聊天等功能。
2. 架构图
房间服务包含两种交互方式:
- 使用PGOS SDK调用房间接口。
- 当特定事件发生时接收房间服务推送的房间通知。
仅在以下事件发生时,房间才会被销毁:
- 房主解散了房间
- 最后一名成员离开房间
- 房间超过7天未活动
玩家只能通过以下方式离开房间:
- 客户端显式调用
LeaveLobby
函数 - 房主可以将任何成员从房间中移除
- 当玩家与PGOS断开连接时,他将自动从房间中移除
请注意,当房间开始战斗时,玩家仍被视为在房间中,核心房间功能仍正常运作。
3. 创建房间配置
在使用房间服务实现您自己的功能之前,您必须先创建一个房间配置。房间配置定义了基本的房间规则,PGOS SDK 需要使用它来创建房间。
由于房间服务与 DS 托管和管理服务配合使用,请确保有可用的 DS Placer 来分配对战会话。如果没有,请先创建一个。有关更多详细信息,请参考以下链接:创建 Placer.
接下来,请按照以下步骤操作:
- 打开 PGOS Web Portal 并进入房间页面。创建一个新的房间配置或从现有配置中克隆。
- 填写配置详细信息。
- 名称:配置名称,必须以字母开头,最多128个字符。名称在游戏区服内必须唯一。
- 准备超时:所有房间玩家进入战斗前准备阶段的最长时间。0s表示准备不会超时。最大值为3600s。
- 默认Placer:选择用于战斗放置的默认Placer。
- 灵活Placer:您可以设置各种游戏模式,并为每种模式分配特定的Placer。在发起战斗后,系统将根据房间提供的游戏模式确定相应的Placer。如果未指定房间的游戏模式,或在门户网站上找不到游戏模式配置,则将使用默认Placer。
- 队伍配置[简单模式]
- 开始对战条件:当房主调用
StartBattle
接口时,根据房间成员的Ready
标记执行不同操作:全员准备否则等待
:房间将进入准备中阶段,等待未准备的玩家准备就绪。全员准备否则报错
:如果不是所有玩家都准备就绪,StartBattle
接口将返回错误。忽略准备标记
:房间将直接进入对战中阶段,忽略玩家的Ready
标记。
队伍选择策略:决定如何在队伍之间为新房间成员选择位置。
平衡玩家数量
:为确保玩家数量平衡,优先加入人数最少且有空位的队伍。集中玩家
:为集中玩家,优先加入人数最多且有空位的队伍。
队伍:房间中的队伍配置。
- 名称:队伍的名称。在房间中必须唯一。
- 容量:队伍中的最大玩家数。所有队伍容量之和即为房间容量。
- 队伍配置 [高级模式] 在高级模式下,您可以创建多个队伍组,每个队伍组可以有自己的开始对战条件配置。
- 队伍选择策略:与简单模式相同。决定如何在具有相同优先级的队伍之间选择位置。
- 队伍群组:允许配置多个队伍群组。每个群组可以配置多个队伍。
- 开始战斗条件:房间所有者调用
StartBattle
接口时的行为取决于房间成员的Ready
标志。您可以为每个群组设置"开始战斗条件"- 忽略准备标志:房间将忽略该群组成员的准备状态。
- 全部准备否则等待/全部准备否则报错:如果该群组的成员未全部准备就绪,房间将进入
准备中
阶段或收到错误。当这两个条件同时出现时,"全部准备否则报错"的优先级更高,因此房间内玩家将收到错误。
4.核心功能
本节演示房间服务的基本功能,错误处理步骤请参考最后一节.
4.1 创建房间
在创建房间的 API 中,可以设置以下选项:
lobby_config
(必需):通过 PGOS 门户网站创建的房间配置的名称。name
(必需): 房间的名称。privacy
(必需):LobbyPrivacy::Visible
或LobbyPrivacy::Invisible
。如果设置为 Invisible,则只能使用房间 id 访问房间 。enable_chatting
(可选):传递true
则为房间创建聊天频道,传递false
则不创建。password
(可选):加入房间所需的密码。kv_data
(可选):包含全局自定义房间数据的映射 。protection
(可选,自 v0.23.0 起): 房间的访问保护策略。枚举值为:PasswordNeeded
:玩家加入房间时需要输入的密码。若选择该策略,password
不能为空,否则创建房间接口会返回失败。FreeAccess
:不限制玩家加入。ApplicationNeeded
:调用JoinLobby接口后会向所有者发送加入请求,需要所有者同意才能成功加入房间 。game_mode
(可选): 房间的游戏模式,开始战斗后会使用传送门上该游戏模式对应的Placer ,如果该游戏模式没有配置Placer ,则会使用传送门上配置的默认Placer 。
从房间模块调用CreateLobby
,如下:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::CreateLobby()
{
FClientCreateLobbyParams Params;
Params.lobby_config = TEXT("config_name");
Params.name = TEXT("A Lobby");
Params.privacy = EClientLobbyPrivacy::LobbyPrivacy_Visible;
Params.enable_chatting = true;
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->CreateLobby(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("CreateLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("CreateLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::CreateLobby()
{
FClientCreateLobbyParams Params;
Params.lobby_config = TEXT("config_name");
Params.name = TEXT("A Lobby");
Params.privacy = EClientLobbyPrivacy::LobbyPrivacy_Visible;
Params.enable_chatting = true;
PGOS_CALL_ASYNC_API(UPgosClientAPICreateLobby::CreateLobby,
this,
&SomeUObjectClass::OnCreateLobbySuccess,
&SomeUObjectClass::OnCreateLobbyFailed,
Params);
}
void SomeUObjectClass::OnCreateLobbySuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("CreateLobbySuccess"))
}
void SomeUObjectClass::OnCreateLobbyFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("CreateLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.2 队伍位置
每个房间中都有多个队伍,每个队伍都有按顺序排列的玩家位置。PGOS的房间服务从1开始为这些位置编号。
- 队伍结构可以在房间配置中设置。
- 创建房间时,房主会被分配到第一个队伍的第一个位置。
- 新玩家进入房间时会自动分配到一个队伍位置。
- 房间成员可以通过调用API
SwitchTeamSlot
自由切换到空闲位置。 - 房间成员可以通过调用API
RequestSwitchTeamSlot
向其他成员发送位置切换请求。 - 如果房间处于准备中阶段,切换队伍位置会导致房间回到等待中阶段。
4.2.1 切换到空闲位置
事件:
OnLobbyTeamUpdated
:如果 API 调用SwitchTeamSlot
成功,将发送给房间内的所有成员。
从 Lobby 模块调用 SwitchTeamSlot
的方法如下:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::SwitchTeamSlot()
{
FString TargetTeamName = ObtainFromSomeWhere();
int32 TargetTeamSlot = ObtainFromSomeWhere();
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->SwitchTeamSlot(TargetTeamName, TargetTeamSlot, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnSwitchTeamSlotSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnSwitchTeamSlotFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::SwitchTeamSlot()
{
FString TargetTeamName = ObtainFromSomeWhere();
int32 TargetTeamSlot = ObtainFromSomeWhere();
PGOS_CALL_ASYNC_API(UPgosClientAPISwitchTeamSlot::SwitchTeamSlot,
this,
&SomeUObjectClass::OnSwitchTeamSlotSuccess,
&SomeUObjectClass::OnSwitchTeamSlotFailed,
TargetTeamName,
TargetTeamSlot);
}
void SomeUObjectClass::OnSwitchTeamSlotSuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnSwitchTeamSlotSuccess"))
}
void SomeUObjectClass::OnSwitchTeamSlotFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnSwitchTeamSlotFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.2.2 请求切换至已占用槽位
事件:
OnLobbySwitchTeamSlotRequest
:此事件将发送给被请求的成员。OnLobbyTeamUpdated
:如果位置切换请求已确认,将发送给房间内的所有成员。
按照以下方式从 Lobby 模块调用 SwitchTeamSlot
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::RequestSwitchTeamSlot()
{
FString TargetTeamName = ObtainFromSomeWhere();
int32 TargetTeamSlot = ObtainFromSomeWhere();
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->RequestSwitchTeamSlot(TargetTeamName, TargetTeamSlot, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnRequestSwitchTeamSlotSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnRequestSwitchTeamSlotFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::RequestSwitchTeamSlot()
{
FString TargetTeamName = ObtainFromSomeWhere();
int32 TargetTeamSlot = ObtainFromSomeWhere();
PGOS_CALL_ASYNC_API(UPgosClientAPIRequestSwitchTeamSlot::RequestSwitchTeamSlot,
this,
&SomeUObjectClass::OnRequestSwitchTeamSlotSuccess,
&SomeUObjectClass::OnRequestSwitchTeamSlotFailed,
TargetTeamName,
TargetTeamSlot);
}
void SomeUObjectClass::OnRequestSwitchTeamSlotSuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnRequestSwitchTeamSlotSuccess"))
}
void SomeUObjectClass::OnRequestSwitchTeamSlotFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnRequestSwitchTeamSlotFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.3 房间自定义数据
我们提供两种类型的房间自定义数据来帮助扩展游戏房间的功能。
房间全局数据: 这些 key-value 键值对适用于整个游戏房间。
- 只有房间的所有者可以修改这些属性。
- 所有玩家都可以读取这些数据,无论他们是否已加入房间。
- 房间全局数据的生命周期与房间一致。
- 当通过房间启动战斗会话时,房间全局数据将写入战斗属性中。
房间玩家数据: 游戏房间中的每个成员都有一组唯一的成员属性。
- 这些数据仅对房间成员可见。
- 成员只能修改自己的成员属性。
- 当玩家离开房间时,房间玩家数据将被移除。 这两种类型的房间自定义数据都可以在不修改其他房间选项的情况下进行更新。
SetLobbyGlobalData
: 此接口用于对房间全局数据进行完整更新。数据更新操作将通过OnLobbyGlobalCustomDataUpdated
事件通知所有房间成员。SetLobbyPlayerData
: 此接口用于更新房间成员的自定义数据。数据更新操作将通过OnLobbyPlayerCustomDataUpdated
事件通知所有房间成员。注意事件
OnLobbyDataUpdated
自 v0.23.0 起已弃用。请改用事件OnLobbyGlobalCustomDataUpdated
和OnLobbyPlayerCustomDataUpdated
。
4.4 加入/离开房间
4.4.1 加入自由访问房间
玩家可以通过调用JoinLobby
来自由加入保护选项为LobbyProtection::FreeAccess
的房间:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::JoinLobby()
{
FClientJoinLobbyParams Params;
Params.lobby_id = TEXT("123456");
Params.password = "";
Params.join_token = "";
Params.payload = "";
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->JoinLobby(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.4.2 加入需要密码的房间
当房间的保护选项设置为LobbyProtection::PasswordNeeded
时,玩家需要提供密码才能通过JoinLobby
加入房间:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::JoinLobby()
{
FClientJoinLobbyParams Params;
Params.lobby_id = TEXT("123456");
Params.password = "password";
Params.join_token = "";
Params.payload = "";
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->JoinLobby(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.4.3 通过邀请加入房间
在OnLobbyInvitation
事件中获得的join_token
可以作为加入受邀房间的token,如果房间保护选项不是FreeAccess
,则可以使用该token成功加入房间。
房间邀请的工作流程如下:
代码示例如下:
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::JoinLobby()
{
FClientJoinLobbyParams Params;
Params.lobby_id = TEXT("123456");
Params.password = "";
Params.join_token = "join token";
Params.payload = "";
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->JoinLobby(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.4.4 加入需要申请的房间
当房间的保护策略为 LobbyProtection::ApplicationNeeded
时,玩家可以通过调用 JoinLobby
接口向房主发送加入请求,由房主决定是否同意该加入请求。
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::JoinLobby()
{
FClientJoinLobbyParams Params;
Params.lobby_id = TEXT("123456");
Params.password = "";
Params.join_token = "";
Params.payload = "custom payload data";
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->JoinLobby(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnJoinLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
当有玩家请求加入房间时,OnLobbyJoinRequest
事件会推送给房主,并且JoinLobbyParams::payload
数据会写入该事件。
房主可以调用ApproveJoinRequest/RejectJoinRequest
接口来处理玩家的加入请求。
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::ApproveJoinRequest()
{
FApproveJoinRequestParams Params;
Params.lobby_id = TEXT("123456");
Params.player_id = "";
Params.token = "token from event OnLobbyJoinRequest";
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->ApproveJoinRequest(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("ApproveJoinRequest"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("ApproveJoinRequest: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
void SomeUObjectClass::RejectJoinRequest()
{
FRejectJoinRequestParams Params;
Params.lobby_id = TEXT("123456");
Params.player_id = "";
Params.token = "token from event OnLobbyJoinRequest";
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->RejectJoinRequest(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("RejectJoinRequest"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("RejectJoinRequest: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
一旦房主处理了请求,游戏客户端将触发 OnLobbyJoinRequestApproved/OnLobbyJoinRequestRejected
事件。
如果房主同意请求,PGOS 将立即把请求发起者添加到房间中。
需要注意的是,以下几种情况可能会导致 OnLobbyJoinRequestApproved
调用失败:
- 请求已过期。每个请求token的有效期为10分钟。
- 玩家处于离线状态。PGOS 不会将离线玩家添加到房间中。
4.4.5 加入房间内的群组
尝试按照优先级值从小到大的顺序加入房间内指定的群组。如果在所有指定的群组中都找不到可用的位置,接口将返回失败。群组内的位置选择将遵循房间配置中的队伍选择策略设置。
需要将房间配置中的队伍配置
切换到高级模式才能启用队伍群组功能。
接口原型:
/**
* Join the specified group in the specified lobby. Try to join the specified groups in the lobby according to the specified priority value from small to large.
* Slot selection within the group follows the Team Selection Strategy configuration on the portal.
* The portal's lobby configuration needs to be switched to advanced mode.
*
* @param Params The information needed when join the lobby.
*/
void JoinLobbyWithGroups(
const FClientJoinLobbyWithGroupsParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientLobbyDetailInfo* Data)> Callback) const;
struct FClientJoinLobbyWithGroupsParams : public FClientJoinLobbyParams
{
/** Try to join the specified groups in the lobby according to the specified priority value from small to large. key: group, value: priority. (priority cannot be less than 0) */
TMap<FString, int32> selected_groups;
};
struct FClientJoinLobbyParams
{
/** The lobby id to join */
FString lobby_id;
/** The lobby password. If the lobby is not encrypted, it doesn't need to be filled. */
FString password;
/**
* It is from the event of 'OnLobbyInvitation' if the player is invited to join a lobby,
* Otherwise it doesn't need to be filled.
*/
FString join_token;
/** Join request payload will be passed to the lobby owner in OnLobbyJoinRequest event for a lobby with LobbyProtection::ApplicationNeeded. */
FString payload;
};
代码示例:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SomeFunction()
{
auto Lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (Lobby)
{
FClientJoinLobbyWithGroupsParams Params;
// Fill Params
Lobby->JoinLobbyWithGroups(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("JoinLobbyWithGroups Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("JoinLobbyWithGroups Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.4.6 加入指定队伍的房间
尝试根据优先级值从小到大加入指定房间中的指定队伍。如果在所有指定的队伍中都找不到可用的位置,接口将返回失败。当多个队伍具有相同的优先级时,位置选择将遵循门户网站上的队伍选择策略配置。 门户网站的房间配置需要切换到高级模式。 接口原型:
/**
* Join the specified team in the specified lobby. Try to join the specified teams in the lobby according to the specified priority value from small to large.
* When multiple teams have the same priority, slot selection follows the Team Selection Strategy configuration on the portal.
* The portal's lobby configuration needs to be switched to advanced mode.
*
* @param Params The information needed when join the lobby.
*/
void JoinLobbyWithTeams(
const FClientJoinLobbyWithTeamsParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientLobbyDetailInfo* Data)> Callback) const;
struct FClientJoinLobbyWithTeamsParams : public FClientJoinLobbyParams
{
/** Try to join the specified teams in the lobby according to the specified priority value from small to large. key: team name, value: priority. (priority cannot be less than 0) */
TMap<FString, int32> selected_teams;
};
代码示例:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SomeFunction()
{
auto Lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (Lobby)
{
FClientJoinLobbyWithTeamsParams Params;
// Fill Params
Lobby->JoinLobbyWithTeams(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("JoinLobbyWithTeams Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("JoinLobbyWithTeams Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.4.7 使用过滤数据快速加入
PGOS将尝试将玩家放入符合房间全局KV数据过滤条件的房间实例中,请注意此操作不保证成功。如果QuickJoin
接口返回失败,游戏可以执行多次自动重试或允许玩家发起重试请求。
以下任一条件的房间无法通过QuickJoin
接口加入:
- 非
Waiting
状态的房间。 - 不可见的房间。
- 非
FreeAccess
保护策略的房间。
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::TryQuickJoin()
{
FQuickJoinLobbyParams Params;
Params.lobby_config = TEXT("123456");
Params.lobby_data;
Params.selected_groups;
Params.selected_teams;
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->QuickJoinLobby(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnQuickJoinSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnQuickJoinFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.4.8 退出房间
玩家在以下情况下会退出房间:
- 通过调用
LeaveLobby
退出房间。 - 已在房间中的玩家加入新房间时会退出之前的房间。
- 玩家离线。
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::LeaveLobby()
{
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->LeaveLobby([](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnLeaveLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnLeaveLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.4.9 Related Events
Event Name | 事件描述 |
---|---|
OnLobbyMemberJoined | 当有人加入当前房间时将触发此事件。 |
OnLobbyMemberLeft | 当有人离开当前房间时将触发此事件。 |
OnLobbyInvitation | 当收到房间邀请时将触发此事件。 |
OnLobbyJoinRequest | 当玩家请求加入保护类型为 LobbyProtection::ApplicationNeeded 的房间时,将触发此事件。 |
OnLobbyJoinRequestApproved | 当加入请求被房间所有者批准时,将触发此事件。 |
OnLobbyJoinRequestRejected | 当房间所有者拒绝加入请求时,将触发此事件。 |
4.5 房间邀请
任何已加入房间的玩家都可以邀请其他玩家加入房间。当玩家收到房间邀请时,他们可以选择接受邀请加入房间或忽略邀请。
事件:
OnLobbyInvitation
:发送给被邀请的玩家。玩家可以接受邀请加入房间或忽略邀请。 按照以下方式调用 Lobby 模块中的InvitePlayer
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::InvitePlayer()
{
FString PlayerId = ObtainFromSomeWhere();
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->InvitePlayer(PlayerId, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnInviteToLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnInviteToLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::InvitePlayer()
{
FString PlayerId = ObtainFromSomeWhere();
PGOS_CALL_ASYNC_API(UPgosClientAPIInvitePlayer::InvitePlayer,
this,
&SomeUObjectClass::OnInviteToLobbySuccess,
&SomeUObjectClass::OnInviteToLobbyFailed,
PlayerID);
}
void SomeUObjectClass::OnInviteToLobbySuccess(FPgosResult Error)
{
UE_LOG(LogTemp, Log, TEXT("OnInviteToLobbySuccess"))
}
void SomeUObjectClass::OnInviteToLobbyFailed(FPgosResult Error)
{
UE_LOG(LogTemp, Log, TEXT("OnInviteToLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.6 搜索房间
4.6.1 基本搜索功能 可以使用以下一个或多个选项搜索大厅:
lobby_config
: 房间配置的名称。status
:您可以按状态搜索大厅:LobbyStatus::Waiting
、LobbyStatus::Preparing
或LobbyStatus::Playing
。或者,将LobbyStatus::Dummy
传递给 API 以搜索具有任何状态的大厅。protection
:您可以按保护类型LobbyProtection::PasswordNeeded
或LobbyProtection::PasswordUnneeded
搜索大厅。或者,将LobbyProtection::Dummy
传递给 API 以搜索具有任何类型保护的大厅。order_by
:搜索结果有多种排序选项,LOBBY_ORDER_BY_DEFAULT
:按状态排序,然后按创建时间排序。LOBBY_ORDER_BY_CREATE_TIME
: 按房间创建时间排序。LOBBY_ORDER_BY_UPDATE_TIME
: 按房间最后更新时间排序。LOBBY_ORDER_BY_JOINED_MEMBER_COUNT
: 按加入成员数量排序。LOBBY_ORDER_BY_MISSING_MEMBER_COUNT
: 按空槽数量排序。
order_type
: 搜索结果的排序方向,LOBBY_ORDER_TYPE_ASCENDING
: 搜索结果的升序排列。LOBBY_ORDER_TYPE_DESCENDING
: 搜索结果的降序排列。❗注意
- 只有使用精确的
lobby_id
才能找到将privacy
设置为LobbyPrivacy::Invisible
的大厅。 - 将
FClientSearchLobbyParams
中的count
设置为 0,仅查询符合搜索条件的大厅数量。 - 将
FClientSearchLobbyParams
中的count
设置为非零值,以查询符合搜索条件的大厅数量和列表,最多为count
中设置的数量。
- 只有使用精确的
从 Lobby 模块调用 SearchLobby
,如下所示:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::SearchLobby()
{
FClientSearchLobbyParams Params;
Params.offset = 0;
Params.count = 20;
Params.protection = EClientLobbyProtection::LobbyProtection_PasswordUnneeded;
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->SearchLobby(Params, [](const FPgosResult& Ret, const FClientSearchLobbyResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnSearchLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnSearchLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::SearchLobby()
{
FClientSearchLobbyParams Params;
Params.offset = 0;
Params.count = 20;
Params.protection = EClientLobbyProtection::LobbyProtection_PasswordUnneeded;
PGOS_CALL_ASYNC_API(UPgosClientAPISearchLobby::SearchLobby,
this,
&SomeUObjectClass::OnSearchLobbySuccess,
&SomeUObjectClass::OnSearchLobbySuccessFailed,
Params);
}
void SomeUObjectClass::OnSearchLobbySuccess(
FPgosResult Error, FClientSearchLobbyResult SearchResult)
{
UE_LOG(LogTemp, Log, TEXT("OnSearchLobbySuccess"))
}
void SomeUObjectClass::OnSearchLobbyFailed(
FPgosResult Error, FClientSearchLobbyResult SearchResult)
{
UE_LOG(LogTemp, Log, TEXT("OnSearchLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.6.2 自定义筛选选项
SearchLobby
接口允许使用房间全局数据来筛选搜索结果。游戏可以在房间实例中自由修改房间全局数据,并在 SearchLobby
接口的 SearchLobbyParams::lobby_data
参数中配置需要筛选的字段。
支持两种筛选方式:
SearchLobbyDataFilter::equal_values
:表示筛选出指定key的值中与列表中任意值匹配的房间实例。SearchLobbyDataFilter::not_equal_values
:表示筛选出指定key中的值与列表中任意值不匹配的房间实例
4.6.3 自定义筛选选项示例
这里有一个使用全局数据定义和筛选游戏模式的典型示例。 首先,定义房间实例数据集:
lobby: 001
kv_data:
"game_mode":"solo_adventure"
lobby: 002
kv_data:
"game_mode":"co_adventure"
lobby: 003
kv_data:
"game_mode":"royale"
lobby: 004
kv_data:
"game_mode":"roguelike"
以下是 SearchLobbyDataFilter
的示例及其对应的筛选结果。
筛选所有游戏模式为 roguelike
和 royale
的房间:
# Show case of SearchLobbyParams::lobby_data
"game_mode":
equal_values: ["roguelike", "royale"]
not_equal_values: []
筛选所有非roguelike
和royale
游戏模式的房间:
# Show case of SearchLobbyParams::lobby_data
"game_mode":
equal_values: []
not_equal_values: ["roguelike", "royale"]
4.7 房间管理
房间所有者可以:
- 修改房间选项
- 踢出成员
- 解散房间
- 开始对战
- 将所有权转移给其他成员
4.7.1 修改房间选项
创建房间后,房间所有者可以修改以下房间选项:
name
password
privacy
-kv_data
game_mode
❗ 注意
传递给 API 的所有值都将替换现有值,但
kv_data
除外。kv_data
仅在非空时有效。要清除kv_data
,请使用SetLobbyData
API 并向其传递一个空映射。
事件:
OnLobbyInfoUpdated
: 当房间所有者更新房间选项时,会发送给房间内的所有成员。
按照以下方式从 Lobby 模块调用 EditLobbyInfo
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::EditLobbyInfo()
{
FClientEditLobbyInfoParams Params;
Params.name = TEXT("New Name");
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->EditLobbyInfo(Params, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnEditLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnEditLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::EditLobbyInfo()
{
FClientEditLobbyInfoParams Params;
Params.name = TEXT("New Name");
PGOS_CALL_ASYNC_API(UPgosClientAPIEditLobbyInfo::EditLobbyInfo,
this,
&SomeUObjectClass::OnEditLobbySuccess,
&SomeUObjectClass::OnEditLobbyFailed,
Params);
}
void SomeUObjectClass::OnEditLobbySuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnEditLobbySuccess"))
}
void SomeUObjectClass::OnEditLobbyFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnEditLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.7.2 设置房间数据
可以在不修改其他房间选项的情况下更新房间数据。请注意,传递给 SetLobbyData
API 的数据将替换现有数据,也就是说,如果您向 API 传递一个空映射,房间数据将被清除。
提示:
您可以使用房间数据来存储自定义房间选项,例如游戏模式和游戏地图。
事件:
OnLobbyDataUpdated
:当房间所有者更新自定义房间数据时,会发送给房间内的所有成员。
请按以下方式调用Lobby模块中的SetLobbyData
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::SetLobbyData()
{
TMap<FString, FString> CustomData;
CustomData.Add(TEXT("GameMode"), TEXT("5v5"));
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->SetLobbyData(CustomData, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnSetLobbyDataSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnSetLobbyDataFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::SetLobbyData()
{
TMap<FString, FString> CustomData;
CustomData.Add(TEXT("GameMode"), TEXT("5v5"));
PGOS_CALL_ASYNC_API(UPgosClientAPISetLobbyData::SetLobbyData,
this,
&SomeUObjectClass::OnSetLobbyDataSuccess,
&SomeUObjectClass::OnSetLobbyDataFailed,
CustomData);
}
void SomeUObjectClass::OnSetLobbyDataSuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnSetLobbyDataSuccess"))
}
void SomeUObjectClass::OnSetLobbyDataFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnSetLobbyDataFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.7.3 踢出成员
房间所有者可以踢出任何其他房间成员以腾出更多团队位置。
事件:
OnLobbyMemberLeft
:当房间所有者将某人踢出房间时,会向房间内剩余成员发送此事件。
按照以下方式调用 Lobby 模块中的 KickOutMember
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::KickOutMember()
{
FString MemberPlayerID = ObtainFromSomeWhere();
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->KickOutMember(MemberPlayerID, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnKickOutMemberSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnKickOutMemberFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::KickOutMember()
{
FString MemberPlayerID = ObtainFromSomeWhere();
PGOS_CALL_ASYNC_API(UPgosClientAPIKickOutMember::KickOutMember,
this,
&SomeUObjectClass::OnKickOutMemberSuccess,
&SomeUObjectClass::OnKickOutMemberFailed,
MemberPlayerID);
}
void SomeUObjectClass::OnKickOutMemberSuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnKickOutMemberSuccess"))
}
void SomeUObjectClass::OnKickOutMemberFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnKickOutMemberFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.7.4 解散房间
在以下情况下房间将被解散:
- 房主解散房间。
- 当所有成员都离开或登出时自动解散。
事件:
OnLobbyDismiss
: 当房间所有者解散房间时,发送给房间内的所有成员
按照以下方式调用 Lobby 模块中的 DismissLobby
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::DismissLobby()
{
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->DismissLobby([](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnDismissLobbySuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnDismissLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::DismissLobby()
{
PGOS_CALL_ASYNC_API(UPgosClientAPIDismissLobby::DismissLobby,
this,
&SomeUObjectClass::OnDismissLobbySuccess,
&SomeUObjectClass::OnDismissLobbyFailed);
}
void SomeUObjectClass::OnDismissLobbySuccess(FPgosResult Error)
{
UE_LOG(LogTemp, Log, TEXT("OnDismissLobbySuccess"))
}
void SomeUObjectClass::OnDismissLobbyFailed(FPgosResult Error)
{
UE_LOG(LogTemp, Log, TEXT("OnDismissLobbyFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.7.5 开始对战
4.7.5.1 玩家状态
每个房间成员都有一个准备标记。玩家可以随时更新此标记。
事件:
OnLobbyTeamUpdated
:当玩家更新其准备状态时,将发送给房间内的所有成员。
从Lobby模块调用ReadyForBattle
,方法如下:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::ReadyForBattle()
{
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->ReadyForBattle(true, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnReadyForBattleSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnReadyForBattleFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::ReadyForBattle()
{
PGOS_CALL_ASYNC_API(UPgosClientAPIReadyForBattle::ReadyForBattle,
this,
&SomeUObjectClass::OnReadyForBattleSuccess,
&SomeUObjectClass::OnReadyForBattleFailed,
true);
}
void SomeUObjectClass::OnReadyForBattleSuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnReadyForBattleSuccess"))
}
void SomeUObjectClass::OnReadyForBattleFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnReadyForBattleFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.7.5.2 房间状态
房间可能处于以下状态之一:
- 等待中:对战尚未开始或已结束
- 准备中:等待玩家标记自己准备开始对战
- 进行中:对战已成功开始
以下流程图展示了房间状态之间的转换关系:
- 等待中 -> 准备中:房间所有者开始战斗,但并非所有玩家都已准备就绪。
- 准备中 -> 等待中:准备中状态超时,或者成员加入或离开房间导致准备中状态中断。
- 等待中 -> 游戏中:房间所有者开始战斗,且房间内所有成员都已准备就绪。
- 游戏中 -> 等待中:战斗结束。
- 准备中 -> 游戏中:未准备就绪的玩家将其准备状态更新为已就绪。
如果StartBattle
接口收到成功响应,意味着新的战斗会话将被部署到专用服务器上。当战斗会话变为已激活状态时,玩家就可以连接到专用服务器。
事件:
OnLobbyStatusChanged
:当房间所有者开始战斗时,发送给房间内的所有成员。OnBattleSessionUpdated
:当战斗会话状态更新时,发送给房间内的所有成员。
按照以下方式从Lobby模块调用StartBattle
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::StartBattle()
{
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->StartBattle([](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnStartBattleSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnStartBattleFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::StartBattle()
{
PGOS_CALL_ASYNC_API(UPgosClientAPIStartBattle::StartBattle,
this,
&SomeUObjectClass::OnStartBattleSuccess,
&SomeUObjectClass::OnStartBattleFailed);
}
void SomeUObjectClass::OnStartBattleSuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnStartBattleSuccess"))
}
void SomeUObjectClass::OnStartBattleFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnStartBattleFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.7.5.3 在专用服务器中处理对战会话
当客户端开始一场对战时,专用服务器会通过回调函数 OnStartBattleSession
接收到对战的详细信息。
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::SomeFunction()
{
auto Hosting = IPgosSDKCpp::Get().GetServerHostingAPI();
if (Hosting)
{
HostingAPI->OnHealthCheck().AddUObject(this, &APGOSBattleGameMode::OnHealthCheck);
HostingAPI->OnStartBattleSession().AddUObject(this, &APGOSBattleGameMode::OnStartBattleSession);
HostingAPI->OnProcessTerminate().AddUObject(this, &APGOSBattleGameMode::OnProcessTerminate);
HostingAPI->OnTerminateBattleSession().AddUObject(this, &APGOSBattleGameMode::OnTerminateBattleSession);
// Notify PGOS that a server process is ready to host a battle session
FPgosResult Result = HostingAPI->ProcessReady(Port, LogPathes,
OnHealthCheckCallback, OnStartBattleSessionCallback, OnProcessTerminateCallback);
}
}
bool SomeUObjectClass::OnHealthCheck()
{
...
}
void SomeUObjectClass::OnStartBattleSession(const FServerBattleSession& BattleSession)
{
...
for (const FServerBattleTeam& TeamData: BattleSession.battle_data.teams)
{
...
for (const FServerPlayerDesc& PlayerDesc: TeamData.players)
{
// PlayerDesc.player_info.player_id may be empty then the team slot has no player
}
}
}
void SomeUObjectClass::OnProcessTerminate(int64 TerminationTime)
{
...
}
void SomeUObjectClass::OnTerminateBattleSession(pgos::pstring battle_session_id)
{
...
}
#include "PgosClientLobbyAPI.h"
#include "PgosServerAPI.h"
void SomeUObjectClass::SomeFunction()
{
UPgosServerAPI::FDelegateOnHealthCheckCallback OnHealthCheckCallback;
UPgosServerAPI::FDelegateOnStartBattleSessionCallback OnStartBattleSessionCallback;
UPgosServerAPI::FDelegateOnProcessTerminateCallback OnProcessTerminateCallback;
OnHealthCheckCallback.BindDynamic(this, &SomeUObjectClass::OnHealthCheck);
OnStartBattleSessionCallback.BindDynamic(this, &SomeUObjectClass::OnStartBattleSession);
OnProcessTerminateCallback.BindDynamic(this, &SomeUObjectClass::OnProcessTerminate);
UPgosServerAPI::ProcessReady(Port, LogPathes,
OnHealthCheckCallback, OnStartBattleSessionCallback, OnProcessTerminateCallback);
}
bool SomeUObjectClass::OnHealthCheck()
{
...
}
void SomeUObjectClass::OnStartBattleSession(const FServerBattleSession& BattleSession)
{
...
for (const FServerBattleTeam& TeamData: BattleSession.battle_data.teams)
{
...
for (const FServerPlayerDesc& PlayerDesc: TeamData.players)
{
// PlayerDesc.player_id may be empty
}
}
}
void SomeUObjectClass::OnProcessTerminate(int64 TerminationTime)
{
...
}
void SomeUObjectClass::OnTerminateBattleSession(pgos::pstring battle_session_id)
{
...
}
从上述代码可以看出,DS可以从OnStartBattleSession
中获取所有队伍和玩家信息。与匹配不同,即使房间中存在空位,房间服务也可以开始对战。在DS中,PlayerDesc
中的空player_id
表示房间中的空位。
4.8.6 转移房主权限给其他成员
在以下情况下房主权限将被转移给其他玩家:
- 房主将房主权限转移给指定玩家。
- 房主离开房间且房间内还有其他玩家时。 在这种情况下,房主权限将随机转移给房间内的另一名玩家。
事件:
OnLobbyOwnerTransferred
: 当房间所有权转移给其他玩家时,会发送给房间内的所有成员。
请按以下方式从 Lobby 模块调用 TransferOwnership
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::TransferOwnership()
{
FString NewOwnerPlayerId = ObtainFromSomeWhere();
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->TransferOwnership(NewOwnerPlayerId, [](const FPgosResult& Ret, const FClientLobbyDetailInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("OnTransferSuccess"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("OnTransferFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
#include "PgosClientLobbyAPI.h"
void SomeUObjectClass::TransferOwnership()
{
FString NewOwnerPlayerId = ObtainFromSomeWhere();
PGOS_CALL_ASYNC_API(UPgosClientAPITransferOwnership::TransferOwnership,
this,
&SomeUObjectClass::OnTransferSuccess,
&SomeUObjectClass::OnTransferFailed,
NewOwnerPlayerId);
}
void SomeUObjectClass::OnTransferSuccess(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnTransferSuccess"))
}
void SomeUObjectClass::OnTransferFailed(
FPgosResult Error, FClientLobbyDetailInfo LobbyDetail)
{
UE_LOG(LogTemp, Log, TEXT("OnTransferFailed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
4.8 房间聊天
房间服务内置了可直接使用的聊天功能。但是,这些服务仅在创建房间.时将enable_chatting
设置为true
时才有效。
- 房间内的任何成员都可以发送聊天消息
- 房间内的任何成员都可以接收聊天消息
- 成员不会收到自己发送的聊天消息
事件:
OnReceiveLobbyChatMsg
:发送给房间内除发送者外的所有成员。
按照以下方式调用 Lobby 模块中的 SendLobbyChatTextMsg
/SendLobbyChatCustomMsg
:
#include "PgosSDKCpp.h"
void SomeUObjectClass::SendLobbyChatTextMsg()
{
auto Lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (Lobby)
{
FClientSendLobbyChatMsgParams Params;
Lobby->SendLobbyChatTextMsg(Params, [](const FPgosResult& Ret, const FClientChatMsgInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SendLobbyChatTextMsg Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendLobbyChatTextMsg Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
void SomeUObjectClass::SendLobbyChatCustomMsg()
{
auto Lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (Lobby)
{
FClientSendLobbyChatMsgParams Params;
Lobby->SendLobbyChatCustomMsg(Params, [](const FPgosResult& Ret, const FClientChatMsgInfo* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SendLobbyChatCustomMsg Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SendLobbyChatCustomMsg Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
4.9 房间事件
总的来说,以下是将发送给房间成员的事件:
Event Name | 事件描述 |
---|---|
OnLobbyTeamUpdated | 当玩家更新其准备状态或更换队伍位置时发送。 |
OnLobbyMemberJoined | 当新玩家加入房间时发送。 |
OnLobbyMemberLeft | 当成员离开房间或被房主踢出房间时发送。 |
OnLobbyInvitation | 当玩家向其他玩家发送房间邀请时触发。 |
OnLobbyInfoUpdated | 当房间选项更新时发送。 |
OnLobbyDataUpdated | 当自定义房间数据更新时发送。 |
OnLobbyDismiss | 当房间被解散时发送。 |
OnLobbyStatusChanged | 当房间状态更新时发送。 |
OnBattleSessionUpdated | 当战斗会话状态更新时发送。 |
OnLobbyOwnerTransferred | 当房间所有权转移时发送。 |
OnReceiveLobbyChatMsg | 当玩家在房间中发送聊天消息时触发。 |
OnLobbySwitchTeamSlotRequest | 当其他房间成员发送队伍位置切换请求时发送。 |
OnLobbySwitchTeamSlotRequestConfirmed | 当团队席位切换请求已确认时发送。 |
OnLobbySwitchTeamSlotRequestRejected | 当团队席位切换请求被拒绝时发送。 |
OnLobbyJoinRequest | 当玩家请求加入需要申请审核的房间(LobbyProtection::ApplicationNeeded)时发送。 |
OnLobbyJoinRequestApproved | 当房间所有者批准加入请求时发送。 |
OnLobbyJoinRequestRejected | 当加入请求被房间所有者拒绝时发送。 |
OnLobbyGlobalCustomDataUpdated | 当房间全局自定义数据更新时发送。 |
OnLobbyPlayerCustomDataUpdated | 当房间玩家自定义数据更新时发送。 |
本节讨论一个特殊事件 OnLobbyInfoUpdated
,当房间信息发生变化时会触发该事件。
通常,房间信息的变化会由更具体的事件来表示。例如,当成员离开房间时会触发 OnLobbyMemberLeft
事件,当新玩家加入房间时会触发 OnLobbyMemberJoined
事件,当房主调用 EditLobbyInfo
修改房间信息时会触发 OnLobbyInfoUpdated
事件。
但是,由于网络问题或其他可能导致持久连接中断的原因,房间信息的变化(如玩家进入或离开房间)可能无法通过持久连接从服务器传达给客户端。如果客户端长时间无法检测到房间信息的变化,可能会导致游戏功能异常。为了解决这个问题,当 PGOS SDK 检测到持久连接中断时,会定期(通常每 30 到 60 秒)获取最新的房间信息,然后通过 OnLobbyInfoUpdated
事件通知游戏。
因此,当游戏收到 OnLobbyInfoUpdated
事件时,可能是由于房主调用了 EditLobbyInfo
,也可能是由于以下事件相关的变化:
OnLobbyTeamUpdated
OnLobbyMemberJoined
OnLobbyMemberLeft
OnLobbyInfoUpdated
OnLobbyDataUpdated
OnLobbyStatusChanged
OnLobbyOwnerTransferred
OnLobbyGlobalCustomDataUpdated
OnLobbyPlayerCustomDataUpdated
您可以使用 UPgosClientEventDispatcher
来处理这些事件的绑定问题。
按如下方式将动态委托绑定到 OnLobbyMemberJoined
:
- UE C++
- UE BP
#include "PgosSDKCpp.h"
#include "Core/PgosErrorCode.h"
void SomeUObjectClass::SomeFunction()
{
auto lobby = IPgosSDKCpp::Get().GetClientLobbyAPI();
if (lobby)
{
lobby->OnLobbyMemberJoined().AddUObject(this, &SomeUObjectClass::OnLobbyMemberJoined);
}
}
void SomeUObjectClass::OnLobbyMemberJoined(const FClientLobbyMemberJoinedEvt& event)
{
UE_LOG(LogTemp, Log, TEXT("OnLobbyMemberJoined"));
}
#include "PgosClientEventDispatcher.h"
void SomeUObjectClass::SomeFunction()
{
auto pDispatcher = UPgosClientEventDispatcher::GetPgosClientEventDispatcher();
if (pDispatcher)
{
pDispatcher->OnPlayerSessionChanged.Clear();
// add delegate callback
UPgosClientEventDispatcher::GetPgosClientEventDispatcher()->OnLobbyMemberJoined.AddDynamic(
this,
&SomeUObjectClass::OnLobbyMemberJoined);
// Before current object is destroyed, you should call
// `UPgosClientEventDispatcher::GetPgosClientEventDispatcher()->RemoveAll(this)`
// to clear event bindings.
}
}
void SomeUObjectClass::OnLobbyMemberJoined(const FClientLobbyMemberJoinedEvt& event)
{
UE_LOG(LogTemp, Log, TEXT("OnLobbyMemberJoined"));
}
5. 网页门户中的房间日志
当创建房间时,PGOS后端会自动生成并存储日志,您可以在PGOS网页门户中找到这些日志。在控制台页面,选择您想要查询的游戏,然后导航至Battle > 房间以查看房间日志列表:
可以通过以下字段筛选日志:
- 房间 ID 或 UUID
- 房间创建者 ID
- 房间配置名称
- 房间状态
点击房间 ID 可查看更多日志详情:
- 房间ID: 在游戏客户端显示的唯一房间ID。
- 房间UUID: PGOS后端使用的唯一房间ID。
- 名称: 房间的名称。
- 房间配置: 房间配置的名称。
- 状态: 当前房间状态。
- 房主: 房间房主的名称。
- 对战会话: 当前对战的详细信息;如果对战尚未开始则为空。
- 聊天频道ID: 房间聊天频道的唯一ID;如果创建房间时将
enable_chatting
设置为false
则为空。 - 成员: 房间成员的操作,如加入、离开和解散房间。
- 状态变更: 房间状态的变更。
- 创建时间: 房间创建的时间。
- 解散时间: 房间解散的时间;如果房间尚未解散则为空。
6. 关键错误处理
这些是主要的错误,但并非全部。对于未在列表中的错误,错误信息本身应该已经说明了问题所在。如果您遇到含糊不清的错误信息或任何其他严重错误,请与我们联系。
Error Code | 相关 API | Handling Suggestion |
---|---|---|
kBackendAcquireLockFailed | All APIs on a specified lobby. Except for CreateLobby or SearchLobby. | 这表示对房间的操作失败。分布式锁机制确保了并发安全性。建议在有限次数内重试直至成功。 |
kBackendHasNoWritePermission | Lobby management related APIs | 这表示操作者没有房间的写入权限。游戏可以向玩家提示类似"您需要成为房间所有者才能执行此操作"的信息。 |
kBackendPlayerNotInLobby | All APIs on a specified lobby. Except for JoinLobby. | 这表示操作者不在房间内,因此没有权限对其执行任何操作。 |
kBackendInvalidLobbyConfig | CreateLobby | 这表示房间配置名称不存在。开发人员应当检查网页后台,确认房间配置是否被意外删除或重命名。 |
kBackendLobbyPasswordIsEmpty | CreateLobby | 这表示该房间受密码保护,但尚未设置具体密码。 |
kBackendLobbyInvalidGameMode | CreateLobby EditLobbyInfo | 这表示指定的游戏模式未在房间配置中预先定义。 |
kBackendIllegalCredential | JoinLobby | 这表示玩家在加入房间时输入了错误的密码。游戏可以向玩家提示"密码错误"这样的信息。 |
kBackendNotEnoughSlots | JoinLobby | 这表示房间已经没有空位可供新玩家加入。游戏可以向玩家显示"房间已满"这样的提示信息。 |
kBackendLobbyWaitForApproval | JoinLobby | 这意味着玩家需要等待房间主人批准其加入请求。 |
kBackendFailedToNotifyLobbyOwner | JoinLobby | 这表示系统未能通知房间所有者关于玩家的加入请求。 |
kBackendLobbyGroupNotExist | JoinLobbyWithGroup | 这表示房间中不包含任何已选择的群组。 |
kBackendLobbyTeamNotExist | JoinLobbyWithTeam | 这表示房间内没有任何一个所选的队伍。 |
kBackendAlreadyInBattle | StartBattle | 这表示房间内已有一个正在进行的战斗会话。在当前战斗会话结束之前,房间无法开始新的战斗。游戏可以为玩家提供重新加入当前战斗会话的入口。 |
kBackendAlreadyPreparing | StartBattle | 表示房间处于准备阶段,主人无法再次开始战斗。 |
kBackendTitleRegionIsClosed | StartBattle | 这表示游戏区服已关闭。游戏应向玩家显示提示信息,如"服务器正在维护中,请稍后再来"。 |
kBackendLobbyStartBattleConditionNotMatch | StartBattle | 这表示并非所有成员都已准备好开始战斗。此错误仅在房间配置中禁用准备阶段时才会出现。 |
kBackendLobbyNameContainProfanityWords | CreateLobby EditLobbyInfo | 这表示房间名称中包含不当用语。游戏可以向玩家提示类似"请删除房间名称中的不当用语"这样的消息。 |
kBackendLobbyPlayerBlockedInvitation | InviteMember | 这表示操作被对方玩家阻止。 |
kBackendCannotSwitchToEmptySlot | RequestSwitchTeamSlot | 这意味着玩家不能请求切换到空位。应该直接调用SwitchTeamSlot 。 |
kBackendLobbyInvalidRequestId | ConfirmSwitchTeamSlot | 这表示切换槽位的请求ID无效。 |
kBackendLobbyOutdatedRequestId | ConfirmSwitchTeamSlot | 这表示请求切换位置的玩家已经移动。 |
kBackendLobbyCustomDataLengthExceeded | SetGlobalCustomData SetPlayerCustomData | 这表示自定义数据超出了长度限制。 |
kBackendLobbyPlayerIsOffline | ApproveJoinLobby | 这表示申请加入房间的玩家已离线。 |
kBackendLobbyJoinTokenInvalid | ApproveJoinLobby | 这表示审批请求的token无效。 |