跳到主要内容

世界大厅

1. 概述

世界大厅是一个允许玩家自由、流畅进出的实体。PGOS 会根据世界大厅过滤规则将进入世界大厅的玩家分配到若干个世界大厅分组(Buecket)中。PGOS 会根据玩家数量、玩家延迟以及世界大厅中战斗会话的配置容量等因素,将世界大厅分组中的玩家分配到多个战斗会话中。 虽然世界大厅和匹配都可以用来聚集相似的玩家,但这两项服务之间存在显著差异:

  • 玩家加入世界大厅时几乎没有等待时间。PGOS 会根据玩家的加入属性高效地识别对应的分组,并迅速将其分配到合适的战斗会话中。
  • 世界大厅服务生成的战斗会话对每个玩家开放,新玩家可以随时加入和离开。
  • 世界大厅服务不支持复杂的匹配规则,开发者只能定义用作玩家分组依据的简单过滤规则。

世界大厅服务可用于实现多种在线游戏功能。典型的应用场景包括:

  • 持久且可自由访问的游戏场景,如游戏房间或游戏中心。
  • 不需要复杂匹配规则的大型游戏。

2. 关键概念

  • 世界大厅:世界大厅是玩家可以自由、流畅进出的实体。PGOS根据世界大厅过滤器将进入世界大厅的玩家分配到若干个世界大厅分组中。
  • 世界大厅过滤器:世界大厅过滤器由游戏配置,用于定义如何将进入世界大厅的玩家分配到不同的世界大厅分组中。
  • 世界大厅分组:满足特定过滤条件的玩家群组。不同分组的玩家将被严格隔离。PGOS遵循负载集中原则,为分组中的玩家分配战斗会话并协调DS资源。在此过程中,PGOS将充分考虑玩家的延迟,以提供更好的游戏体验。
  • 战斗会话:战斗会话承载分组中的部分玩家,并维护DS资源的访问信息。

3. 架构图

世界大厅服务包含两种交互方式:

  • 使用PGOS SDK调用世界大厅服务接口。
  • 当特定事件发生时接收PGOS推送的世界大厅服务通知。

image-20240826162650709

4. 设置大厅配置

4.1 创建过滤器

大厅过滤器定义了如何将进入大厅的玩家分配到不同的大厅分组中。每个过滤器可以被多个大厅配置重复使用。使用 PGOS 网页控制台来管理大厅过滤器。

4.1.1 按照以下步骤创建过滤器

  1. 访问网页控制台

  2. 进入控制台的 Battle/World 模块。

  3. 选择页面顶部的"过滤器"选项卡。

  4. 您可以创建新的过滤器或编辑现有的过滤器。

  5. image-20231117104944427

  6. 填写筛选器详细信息。

    • 名称:创建一个有意义的名称,以便您可以在列表中轻松识别它。筛选器名称在游戏区服中必须是唯一的,该名称将在后续创建世界大厅配置时使用。
    • 描述:(可选)筛选器的描述。
    • 属性:定义筛选器所需的玩家属性数据,这些属性将在筛选规则中使用。当玩家请求加入世界大厅时,世界大厅服务将读取玩家的属性,并根据筛选规则中定义的约束条件来确定玩家所属的分组。
    • 规则:在此定义如何对世界大厅中的玩家进行分组。请注意,这些规则是层层叠加的,这意味着定义的筛选规则越多,在运行时生成的分组就越多。

4.1.2 属性字段

image-20231120110139821

  • Data Source(数据源):数据来源告诉世界大厅服务从哪里读取过滤规则所使用的玩家属性。支持的数据来源枚举如下:
    • Client Request(客户端请求):世界大厅服务将从客户端的JoinWorld请求中读取该属性值。
    • Player Data(玩家数据):世界大厅服务将从PGOS玩家KV数据服务中获取所需的属性值。如果选择此类型,则需要提供玩家KV数据键。
    • System(系统):PGOS提供的预定义属性,目前仅包含发布平台。
  • Key(键值):仅当数据来源为玩家数据时才填写此字段,用于指定世界大厅服务将读取的玩家KV数据字段。
  • Name(名字):为定义的属性命名,在同一过滤器中不能重复。
  • Default Value(默认值):当世界大厅服务无法从指定的数据来源获取该属性的值时,将使用此默认值。
  • Value Declare(声明值):您可以在此选择是否声明该属性的可用枚举值。如果在此处填写了声明值,可以在过滤规则中定义对未声明值的处理方式。

4.1.3 Rules Fields

image-20240131103019280

  • Rule Name(规则名称):为定义的规则命名,同一个过滤器中不能重复。

  • Attribute(属性):选择该规则要过滤的玩家属性,每个属性只能配置一条规则。

  • Filter Type(过滤类型):目前仅支持Enum类型,表示该规则会检查每个加入玩家的属性,然后将不同属性值的玩家分配到不同的bucket中。

  • Undeclared Data Strategy(未声明数据策略):当属性值未在过滤属性值声明中注册时,该字段定义世界大厅服务如何处理世界大厅的加入请求。

    • Reject(拒绝):当属性值未在过滤属性值声明中注册时,任何世界大厅加入请求都将被拒绝。
    • Accept(接受):当属性值未在过滤属性值声明中注册时,每个世界大厅加入请求都将被接受。
  • Equivalent Values(等效值):允许属性有多组等效值。游戏可以使用此配置来合并不希望将玩家转移到不同存储桶中的属性值。

警告
  1. 未声明的数据策略 设置为 接受 意味着可能会生成较大的运行时世界大厅存储桶。请谨慎做出决定!
  2. 过滤规则项的声明顺序很重要!它决定了存储桶路径的组成,将用于快速定位属于加入世界大厅玩家的存储桶。
  3. 如果玩家在生成存储桶路径时提交的值达到等效值,则此属性的值将更改为等效值配置列表中的第一个项。例如,属性 mode 的等效值为 [a,b,c]。无论玩家提交的是 a、b 还是 c,写入存储桶路径的值始终是 a。

4.1.4 删除过滤器

  1. 您必须确保当前规则集没有关联的匹配配置。否则,将出现错误提示且删除操作将失败。在这种情况下,您必须更改匹配配置并使用其他规则集,然后才能删除该规则集。
  2. 在规则集控制台页面,选择一个规则集并点击"删除"。

4.2 创建 DS Placer

Placer 用于确定运行战斗会话的最佳 Fleet。您可以使用现有的 Placer 或为匹配创建新的 Placer。点击 创建 Placer 链接了解更多详情:

4.3 创建大厅配置

创建大厅过滤器和 DS 托管 Placer 后,需要设置大厅配置以完成大厅创建。使用 PGOS 网页控制台 管理大厅配置。

提示

您可以通过设置多个大厅配置在 PGOS 中创建和维护多个大厅。玩家可以同时加入多个大厅,PGOS 将只为每个大厅中的玩家分配一个战斗会话。

按照以下步骤创建大厅配置:

  1. 打开网页控制台,在控制台中选择战斗/大厅模块。然后在页面顶部选择"大厅配置"选项卡。
  2. 点击"创建"按钮创建新的大厅配置。

image-20231122113806272

  1. 填写比赛配置详情。
    • Name(名称):创建一个有意义的世界大厅配置名称。 世界大厅配置名称在游戏区服内必须是唯一的。
    • Description(说明):(可选)添加世界大厅配置的说明。
    • World Filter(世界大厅过滤器):选择与此世界大厅配置一起使用的过滤器。 **
    • Placer :选择与此世界大厅配置一起使用的Placer器。 世界大厅生成的所有战斗会话都将放置在此Placer下的DS资源中。
    • DS Approval(DS批准):选择DS批准策略以指定如何处理客户端 JoinWorldBattleSession 请求。
      • 会话锁定时需要批准:只有直接加入锁定的世界大厅 战斗会话时才需要DS批准。
      • 所有会话加入请求都需要批准:来自 JoinWorldBattleSession 界面的所有加入请求都需要DS批准,无论世界大厅 战斗会话是否被锁定。
    • Max Players Per Session(每个会话的最大玩家数量):此世界大厅生成的每个战斗会话的最大玩家数量。
    • Max Sessions Count per Bucket(每个 Bucket 的最大运行战斗会话数):每个世界大厅 Bucket 的最大运行战斗会话数,0 表示不限制,必须大于 0。当 Bucket 的运行战斗会话数达到上限时,JoinWorld 接口将返回失败。
    • Max Runtime Buckets Count(行时最大 Buckets 数): 世界大厅运行过程中非空 Bucket 的最大数量,一旦 Bucket 数量达到此值,任何加入世界大厅以创建新 Bucket 的请求都将被拒绝,直到 Bucket 数量低于此限制。
提示 此参数旨在限制由于游戏误用或外界恶意攻击导致的 Bucket 数量激增。Bucket 数量激增可能导致DS资源开销意外增大,必须极力避免。
  • 世界大厅会话清理时间:当世界大厅 战斗会话空置一定时间后,PGOS 将对其进行清理,设置为 0 表示立即清理,最大时间为 3600,单位为秒。

  • 战斗属性:(可选)当通过匹配创建新的战斗会话时,战斗属性将被传递到您的游戏服务器。

5. 将世界大厅集成到游戏客户端

5.1 加入世界大厅

只要玩家符合世界大厅过滤器的限制条件,就可以自由加入世界大厅。需要注意的是,虽然玩家可以加入多个世界大厅,但同一时间只能加入指定世界大厅的一个战斗会话。他们不能同时加入同一个世界大厅的多个战斗会话。

5.1.1 单人加入世界大厅

玩家可以使用 JoinWorld API 请求后端将自己添加到指定世界大厅。后端随后会为该玩家分配一个合适的世界大厅战斗会话。以下是 JoinWorld API 的原型:

/**
* Request to join a world.
*
* @param Params Request struct for join a world.
*/
void JoinWorld(
const FClientJoinWorldParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientJoinWorldResult* Data)> Callback) const;

struct FClientJoinWorldParams
{
/** World config name that request to join. */
FString world_config;
/** Attributes needed by world buckets filters, map key: attribute name, map value: attribute value. */
TMap<FString, FKVDataValue> filter_attributes;
};

struct FClientJoinWorldResult
{
/** The battle session that the world assigned for the players. */
FClientBattleSessionInfo battle_session;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientJoinWorldParams Params;
Params.world_config = TEXT("World_001");
Params.filter_attributes.Add(TEXT("game_mode"), TEXT("happy_play"));
World->JoinWorld(Params, [](const FPgosResult& Ret, const FClientJoinWorldResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("JoinWorld Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("JoinWorld Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
注意

游戏客户端需要在 JoinWorld API 返回成功后持续关注战斗会话的 status 状态。因为只有当战斗会话状态标记为已激活时,BattleSessionInfo 中的 ip_addressport 才会被填充。当玩家的加入行为触发创建新的战斗会话甚至触发 CVM 实例扩容时,JoinWorld API 返回的战斗会话将不会处于已激活状态。 在这种情况下,您需要监听 OnWorldBattleSessionUpdated 事件,以便在战斗会话标记为已激活时获取 ip_addressport

5.1.2 多人加入世界大厅

玩家可以使用 InviteJoinWorld API 邀请其他玩家一起加入指定世界大厅。如果该 API 调用成功,这些被邀请的玩家将在其游戏客户端收到 OnJoinWorldInvitationUpdated 事件。接受邀请的玩家(调用 AcceptJoinWorldInvitation API)将被分配到与邀请者相同的世界大厅战斗会话中。拒绝邀请的玩家(调用 RejectJoinWorldInvitation API)将跳过邀请且不受影响。邀请者也将收到 OnJoinWorldInvitationUpdated 事件以了解谁接受或拒绝了邀请。邀请者被默认为自动接受邀请。一般流程如下图所示:

sequenceDiagram participant Player A participant PGOS Backend participant Player B participant Player C participant DS Player A->>PGOS Backend: Invite A and B to join world together PGOS Backend-->>Player B: Push 'OnJoinWorldInvitationUpdated' event PGOS Backend-->>Player C: Push 'OnJoinWorldInvitationUpdated' event Player B->>PGOS Backend: Accept or reject the invitation PGOS Backend-->>Player A: Push 'OnJoinWorldInvitationUpdated' event PGOS Backend-->>Player B: Push 'OnJoinWorldInvitationUpdated' event PGOS Backend-->>Player C: Push 'OnJoinWorldInvitationUpdated' event Player C->>PGOS Backend: Accept or reject the invitation PGOS Backend->>PGOS Backend: Find or create a world battle session to place A, B, and C in, since all players have made their decision. PGOS Backend-->>Player A: Push 'OnJoinWorldInvitationUpdated' event: Completed PGOS Backend-->>Player B: Push 'OnJoinWorldInvitationUpdated' event: Completed PGOS Backend-->>Player C: Push 'OnJoinWorldInvitationUpdated' event: Completed PGOS Backend-->>Player A: Push 'OnWorldBattleSessionUpdated' event Player A->>DS: Connect (if not connected yet) PGOS Backend-->>Player B: Push 'OnWorldBattleSessionUpdated' event Player B->>DS: Connect (if not connected yet) PGOS Backend-->>Player C: Push 'OnWorldBattleSessionUpdated' event Player C->>DS: Connect (if not connected yet)
注意

在上述流程中,通常情况下,接受邀请的玩家在邀请状态标记为"已完成"后会收到 OnWorldBattleSessionUpdated 事件。但是,如果玩家在收到邀请之前已经在指定的战斗会话中,并且已经获取了战斗会话的 IP 和端口(这意味着他已经收到了战斗会话状态为 Active 的相关事件),那么他可能不会收到 OnWorldBattleSessionUpdated 事件推送。 加入邀请有超时配置(可在控制台中配置),当邀请超时时,未做出决定的玩家将被忽略。

以下是 InviteJoinWorld API 原型:

/**
* Invite other players to join a world together.
*
* @param Params Request struct for inviting other players to join a world together.
*/
void InviteJoinWorld(
const FClientInviteJoinWorldParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientInviteJoinWorldResult* Data)> Callback) const;

struct FClientInviteJoinWorldParams
{
/** World config name that request to join. */
FString world_config;
/** Attributes needed by world buckets filters, map key: attribute name, map value: attribute value. */
TMap<FString, FKVDataValue> filter_attributes;
/**
* Player IDs who will be invited to join the same world. (Must include at least 1 player)
*/
TArray<FString> invited_player_ids;
};

struct FClientInviteJoinWorldResult
{
/** The world configuration name associated with the invitation ticket. */
FString world_config;
/** The ticket of the join world invitation. */
FString invitation_ticket;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientInviteJoinWorldParams Params;
Params.world_config = TEXT("World_001");
Params.filter_attributes.Add(TEXT("game_mode"), TEXT("happy_play"));
Params.invited_player_ids.Add("11223344");
Params.invited_player_ids.Add("55667788");
World->InviteJoinWorld(Params, [](const FPgosResult& Ret, const FClientInviteJoinWorldResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("InviteJoinWorld Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("InviteJoinWorld Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

// monitor the OnJoinWorldInvitationUpdated event
void SomeUObjectClass::OnJoinWorldInvitationUpdated(const FClientJoinWorldInvitationUpdatedEvt& event) {
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (event.info.my_action_to_invitation == EClientJoinWorldInvitationAction::Undecided && World)
{
BOOL WantAccept = TRUE;
if (WantAccept)
{
FClientAcceptJoinWorldInvitationParams Params;
Params.invitation_ticket = event.info.invitation_ticket;
World->AcceptJoinWorldInvitation(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("AcceptJoinWorldInvitation Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("AcceptJoinWorldInvitation Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
else
{
FClientRejectJoinWorldInvitationParams Params;
Params.invitation_ticket = event.info.invitation_ticket;
World->RejectJoinWorldInvitation(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("RejectJoinWorldInvitation Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("RejectJoinWorldInvitation Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
}

5.2 加入世界大厅战斗会话

PGOS 提供了 API 来加入现有的世界大厅 战斗会话 ,我们还提供了 QueryPlayerWorldBattleSessions用于查询任何玩家的世界大厅 战斗会话的 API。通过利用这两个 API,您可以轻松加入任何玩家的世界大厅 战斗会话 ,只要有足够的可用位置。请注意,在以下两种情况下,玩家的世界大厅 战斗会话加入请求 需要DS批准

  1. 请求的世界大厅 战斗会话被DS 锁定
  2. 世界大厅配置的 DS Approval 属性设置为 需要批准所有会话加入请求。 在这两种情况下,您都会收到一个玩家战斗会话状态为 Pending 的战斗会话 ,这表明 需要DS批准才能使用玩家战斗会话 。一旦DS完成批准过程,玩家战斗会话状态将更改为 Reserved,表明玩家战斗会话现在可用,您可以将游戏客户端连接到DS 。

5.2.1 单人加入世界

玩家可以使用 JoinWorldBattleSession API 直接加入指定的世界大厅 战斗会话 。如 5.1.1 单人加入世界大厅部分:API调用成功后,你仍需要关注获取到的战斗会话的status,如果其状态为Placing,则需要关注后续的OnWorldBattleSessionUpdated 事件 ,这样当战斗会话被标记为“Active”时,您就可以获取ip_addressport。以下是InviteJoinWorld API原型:

/**
* Request to join a world battle session directly.
* The API will return failure when there are not enough slots left in the battle session.
* The API will return failure when the battle session is locked.
*
* @param Params Request struct for join a world battle session.
*/
void JoinWorldBattleSession(
const FClientJoinWorldBattleSessionParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientJoinWorldBattleSessionResult* Data)> Callback) const;

struct FClientJoinWorldBattleSessionParams
{
/** World config name that request to join. */
FString world_config;
/** The world battle session id to join. */
FString battle_session_id;
/**
* The game custom data, which will be passed to the DS (dedicated server).
* It can be obtained by monitoring the 'OnBattleSessionUpdated' event of the Hosting module: FServerBattleSessionUpdatedEvt.new_player_battle_sessions.payload.
*/
FString payload;
};

struct FClientJoinWorldBattleSessionResult
{
/** Battle session detail info. */
FClientBattleSessionInfo battle_session;
};

示例代码:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction(const FString& battle_session_id)
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientJoinWorldBattleSessionParams Params;
Params.world_config = TEXT("World_001");
Params.battle_session_id = battle_session_id;
Params.payload = TEXT("{}");
World->JoinWorldBattleSession(Params, [](const FPgosResult& Ret, const FClientJoinWorldBattleSessionResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("JoinWorldBattleSession Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("JoinWorldBattleSession Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

5.2.2 多人加入会话

玩家可以使用 InviteJoinWorldBattleSession API 邀请其他玩家一起加入指定的世界大厅战斗会话。如果此 API 调用成功,这些被邀请的玩家将在其游戏客户端收到 OnJoinWorldInvitationUpdated 事件。接受邀请的玩家(调用 AcceptJoinWorldInvitation API)将被分配到与邀请者相同的世界大厅战斗会话中。拒绝邀请的玩家(调用 RejectJoinWorldInvitation API)将跳过邀请且不会受到影响。邀请者也会收到 OnJoinWorldInvitationUpdated 事件以了解谁接受或拒绝了邀请。邀请者被默认为自动接受邀请。一般流程如下图所示:

sequenceDiagram participant Player A participant PGOS Backend participant Player B participant Player C participant DS Player A->>PGOS Backend: Invite A and B to join world battle session together PGOS Backend-->>Player B: Push 'OnJoinWorldInvitationUpdated' event PGOS Backend-->>Player C: Push 'OnJoinWorldInvitationUpdated' event Player B->>PGOS Backend: Accept or reject the invitation PGOS Backend-->>Player A: Push 'OnJoinWorldInvitationUpdated' event PGOS Backend-->>Player B: Push 'OnJoinWorldInvitationUpdated' event PGOS Backend-->>Player C: Push 'OnJoinWorldInvitationUpdated' event Player C->>PGOS Backend: Accept or reject the invitation PGOS Backend->>PGOS Backend: Try to place A,B and C to the target world battle session, since all players have made their decision. PGOS Backend-->>Player A: Push 'OnJoinWorldInvitationUpdated' event: Completed PGOS Backend-->>Player B: Push 'OnJoinWorldInvitationUpdated' event: Completed PGOS Backend-->>Player C: Push 'OnJoinWorldInvitationUpdated' event: Completed PGOS Backend-->>Player A: Push 'OnWorldBattleSessionUpdated' event Player A->>DS: Connect (if not connected yet) PGOS Backend-->>Player B: Push 'OnWorldBattleSessionUpdated' event Player B->>DS: Connect (if not connected yet) PGOS Backend-->>Player C: Push 'OnWorldBattleSessionUpdated' event Player C->>DS: Connect (if not connected yet)
注意

在上述流程中,通常情况下,接受邀请的玩家在邀请状态标记为"已完成"后会收到 OnWorldBattleSessionUpdated 事件。但是,如果玩家在收到邀请之前已经在指定的战斗会话中,并且已经获得了战斗会话的 IP 和端口(这意味着他已经收到了战斗会话状态为"活跃"的相关事件),那么他可能不会收到 OnWorldBattleSessionUpdated 事件推送。 加入邀请有超时配置(可在控制台中配置),当邀请超时时,未做出决定的玩家将被忽略。

以下是 InviteJoinWorldBattleSession API 原型:

/**
* Invite other players to join a world battle session directly together.
* The API will return failure when there are not enough slots left in the battle session.
* The API will return failure when the battle session is locked.
*
* @param Params Request struct for inviting other players to join a world battle session.
*/
void InviteJoinWorldBattleSession(
const FClientInviteJoinWorldBattleSessionParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientInviteJoinWorldBattleSessionResult* Data)> Callback) const;

struct FClientInviteJoinWorldBattleSessionParams
{
/** World config name that request to join. */
FString world_config;
/** The world battle session id to join. */
FString battle_session_id;
/**
* The game custom data, which will be passed to the DS (dedicated server).
* It can be obtained by monitoring the Hosting module event (OnBattleSessionUpdatedEvt): BattleSessionUpdatedEvt.new_player_battle_sessions.payload.
*/
FString payload;
/**
* Player IDs who will be invited to join the same world battle session. (Must include at least 1 player)
*/
TArray<FString> invited_player_ids;
};

struct FClientInviteJoinWorldBattleSessionResult
{
/** The world configuration name associated with the invitation ticket. */
FString world_config;
/** The ticket of the join world invitation. */
FString invitation_ticket;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction(const FString& battle_session_id)
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientInviteJoinWorldBattleSessionParams Params;
Params.world_config = TEXT("World_001");
Params.battle_session_id = battle_session_id;
Params.payload = TEXT("{}");
Params.invited_player_ids.Add("11223344");
Params.invited_player_ids.Add("55667788");
World->InviteJoinWorldBattleSession(Params, [](const FPgosResult& Ret, const FClientInviteJoinWorldBattleSessionResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("InviteJoinWorldBattleSession Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("InviteJoinWorldBattleSession Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

// monitor the OnJoinWorldInvitationUpdated event
void SomeUObjectClass::OnJoinWorldInvitationUpdated(const FClientJoinWorldInvitationUpdatedEvt& event) {
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (event.info.my_action_to_invitation == EClientJoinWorldInvitationAction::Undecided && World)
{
BOOL WantAccept = TRUE;
if (WantAccept)
{
FClientAcceptJoinWorldInvitationParams Params;
Params.invitation_ticket = event.info.invitation_ticket;
World->AcceptJoinWorldInvitation(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("AcceptJoinWorldInvitation Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("AcceptJoinWorldInvitation Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
else
{
FClientRejectJoinWorldInvitationParams Params;
Params.invitation_ticket = event.info.invitation_ticket;
World->RejectJoinWorldInvitation(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("RejectJoinWorldInvitation Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("RejectJoinWorldInvitation Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}
}

5.3 退出世界大厅

5.3.1 单人退出世界大厅

如果玩家想要退出当前所在的世界大厅战斗会话,可以使用LeaveWorld接口通知后端并释放世界大厅战斗会话槽位。以下是LeaveWorld接口的原型:

/**
* Request to join a world
*
* @param Params Request struct for leave a world.
*/
void LeaveWorld(
const FClientLeaveWorldParams& Params,
TFunction<void(const FPgosResult& Ret)> Callback) const;

struct FClientLeaveWorldParams
{
/** World config name that request to leave. */
FString world_config;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientLeaveWorldParams Params;
Params.world_config = TEXT("World_001");
World->LeaveWorld(Params, [](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("LeaveWorld Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("LeaveWorld Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

5.3.2 多人离开世界大厅

玩家可以使用 InviteLeaveWorld API 邀请其他玩家离开指定世界大厅。如果此 API 调用成功,这些被邀请的玩家将在其游戏客户端收到 OnLeaveWorldInvitationUpdated 事件。接受邀请的玩家(调用 AcceptLeaveWorldInvitation API)将直接离开世界大厅战斗。拒绝邀请的玩家(调用 RejectLeaveWorldInvitation API)将跳过邀请且不受影响。邀请者也将收到 OnLeaveWorldInvitationUpdated 事件以了解谁接受或拒绝了邀请。邀请者在调用 InviteLeaveWorld 接口时将直接离开世界大厅。一般流程如下图所示:

sequenceDiagram participant Player A participant PGOS Backend participant Player B Player A->>PGOS Backend: A invite B to leave a world together PGOS Backend->>PGOS Backend: Remove A from the world(battle session) PGOS Backend-->>Player A: Push OnWorldBattleSessionUpdated with player battle session: Completed PGOS Backend-->>Player B: Push 'OnLeaveWorldInvitationUpdated' event Player B->>PGOS Backend: Accept the leave invitation PGOS Backend->>PGOS Backend: Remove B from the world(battle session) PGOS Backend-->>Player B: Push OnWorldBattleSessionUpdated with player battle session: Completed PGOS Backend-->>Player A: Push 'OnLeaveWorldInvitationUpdated' event: Completed PGOS Backend-->>Player B: Push 'OnLeaveWorldInvitationUpdated' event: Completed

以下是 InviteLeaveWorld API 的原型:

/**
* Invite other players to leave a world together.
*
* @param Params Request struct for inviting other players to leave a world together.
* @param ResultDelegate The result delegate after the API execution ends, and it will be called in the GAME THREAD.
*/
void InviteLeaveWorld(
const FClientInviteLeaveWorldParams& Params,
FPgosClientOnInviteLeaveWorld ResultDelegate) const;

struct PGOSSDKCPP_API FClientInviteLeaveWorldParams
{
/** World config name that request to join. */
FString world_config;
/**
* Player IDs who will be invited to leave the same world. (Must include at least 1 player)
* These invited players will receive the 'OnLeaveWorldInvitationUpdated' event on their game client.
* Players who accept (AcceptLeaveWorldInvitation API) the invitation will be removed from the world.
* Players who reject (RejectLeaveWorldInvitation API) the invitation will skip the invitation and will not be affected.
* The inviter (current player) will also receive the 'OnLeaveWorldInvitationUpdated' event to know the status of the invitation being accepted or rejected.
* The inviter will automatically leave the world.
*/
TArray<FString> invited_player_ids;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientInviteLeaveWorldParams Params;
Params.world_config = TEXT("World_001");
Params.invited_player_ids.Add("player_to_invite");
World->InviteLeaveWorld(Params, [](const FPgosResult& Ret, const FClientInviteLeaveWorldResult* Data)> ResultCallback) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("InviteLeaveWorld Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("InviteLeaveWorld Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

5.4 查询世界大厅信息

PGOS提供了查询世界大厅相关信息的API接口。

5.4.1 查询我的世界大厅对战会话

玩家可以使用QueryMyWorldBattleSessions API来查询他们的世界大厅对战会话。 以下是QueryMyWorldBattleSessions API的原型:

/**
* Specify the world config names to query if the current player has joined them.
* Corresponding battle session id will be returned for the worlds in which the player has joined.
*/
void QueryMyWorldBattleSessions(
const FClientQueryMyWorldBattleSessionsParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientQueryMyWorldBattleSessionsResult* Data)> Callback) const;

struct FClientQueryMyWorldBattleSessionsParams
{
/** Worlds to query. */
TArray<FString> world_configs;
};

struct FClientQueryMyWorldBattleSessionsResult
{
/** A map from world config name to the battle session. Only contains the worlds that the player has joined. */
TMap<FString, FClientBattleSessionInfo> world_battle_sessions;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientQueryMyWorldBattleSessionsParams Params;
Params.world_configs.Add(TEXT("World_001"));
Params.world_configs.Add(TEXT("World_002"));
World->QueryMyWorldBattleSessions(Params, [](const FPgosResult& Ret, const FClientQueryMyWorldBattleSessionsResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("QueryMyWorldBattleSessions Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("QueryMyWorldBattleSessions Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

5.4.2 查询玩家世界大厅对战会话

玩家可以使用QueryPlayerWorldBattleSessions API来查询其他玩家的世界大厅对战会话。 以下是QueryPlayerWorldBattleSessions API的原型:

/**
* Specify the world config names to query if a player has joined them.
* Corresponding battle session id will be returned for the worlds in which the player has joined.
*/
void QueryPlayerWorldBattleSessions(
const FClientQueryPlayerWorldBattleSessionsParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientQueryPlayerWorldBattleSessionsResult* Data)> Callback) const;

struct FClientQueryPlayerWorldBattleSessionsParams
{
/** Player to query. */
FString player_id;
/** Worlds to query. */
TArray<FString> world_configs;
};

struct FClientQueryPlayerWorldBattleSessionsResult
{
/** A map from world config name to the battle session. Only contains the worlds that the player has joined. */
TMap<FString, FClientBattleSessionInfo> world_battle_sessions;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction()
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientQueryPlayerWorldBattleSessionsParams Params;
Params.player_id = TEXT("11223344");
Params.world_configs.Add(TEXT("World_001"));
Params.world_configs.Add(TEXT("World_002"));
World->QueryPlayerWorldBattleSessions(Params, [](const FPgosResult& Ret, const FClientQueryPlayerWorldBattleSessionsResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("QueryPlayerWorldBattleSessions Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("QueryPlayerWorldBattleSessions Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

5.4.3 查询加入世界大厅邀请

玩家可以使用QueryJoinWorldInvitation API来查询指定邀请码的加入世界大厅邀请的详细信息。 以下是QueryJoinWorldInvitation API的原型:

/**
* Query the detail information of the join world invitation for the specified invitation ticket.
*/
void QueryJoinWorldInvitation(
const FClientQueryJoinWorldInvitationParams& Params,
TFunction<void(const FPgosResult& Ret, const FClientQueryJoinWorldInvitationResult* Data)> Callback) const;

struct FClientQueryJoinWorldInvitationParams
{
/** The invitation ticket to query. */
FString invitation_ticket;
};

struct FClientQueryJoinWorldInvitationResult
{
/** The join world invitation detail information. */
FClientJoinWorldInvitationInfo info;
};

代码示例:

#include "PgosSDKCpp.h"

void SomeUObjectClass::SomeFunction(const FString& invitation_ticket)
{
auto World = IPgosSDKCpp::Get().GetClientWorldAPI();
if (World)
{
FClientQueryJoinWorldInvitationParams Params;
Params.invitation_ticket = invitation_ticket;
World->QueryJoinWorldInvitation(Params, [](const FPgosResult& Ret, const FClientQueryJoinWorldInvitationResult* Data) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("QueryJoinWorldInvitation Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("QueryJoinWorldInvitation Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

5.5 监控事件

游戏开发者需要监控和处理以下事件以确保您的世界能够正常运行。您可以阅读此处)了解如何监控事件。

5.5.1 OnWorldBattleSessionUpdated 事件

当战斗会话状态发生变化时,将触发此事件。需要注意的是,只有当战斗会话处于Active状态时,您才能从FClientBattleSessionInfo结构中获取有效的 DS 的 IP 和端口。

struct FClientWorldBattleSessionUpdatedEvt
{
/** The world configuration name associated with this update. */
FString world_config;
/** The updated details of the battle session that the current player joined. */
FClientBattleSessionInfo battle_session;
};

struct FClientBattleSessionInfo
{
/** The battle session id to query */
FString battle_session_id;
/** The Unix timestamp(in seconds) when the battle session was created. */
int64 created_time = -1;
/** The IP address of the DS holding the battle session, and it will be assigned the value when the status is 'Active'. */
FString ip_address;
/** The port of the DS holding the battle session, and it will be assigned the value when the status is 'Active'. */
int32 port = 0;
/** The status of the battle session. */
EClientBattleSessionStatus status = EClientBattleSessionStatus::Dummy;
/** The message of the battle status */
FString status_msg;
/** The Unix timestamp(in seconds) for the last update in the battle status */
int64 last_updated_time = -1;
/** The Unix timestamp(in seconds) when the player disconnected from game server. */
int64 terminated_time = -1;
/** Timeout for placing status */
int32 placing_timeout = -1;
/** Player battle session of current player */
FClientPlayerBattleSessionInfo cur_player_battle_session_info;
/** K-V pair property of a battle session */
TArray<FBattleProperty> battle_properties;
/** Custom string data of a battle session. Preset in matchmaking configuration */
FString battle_session_data;
/** What service that the battle session generated from */
EBattleSourceType source_type = EBattleSourceType::None;
/** Configuration name of matchmaking, lobby or world service */
FString configuration_name;
/** Mapping from bucket filter attribute name to attribute value. [for WORLD battle session only] */
TMap<FString, FString> bucket_path;
/** Ruleset name hit. Filled when battle session source type is BattleSourceType::Matchmaking */
FString ruleset_name;
/** True if the battle session is locked by game server. */
bool locked = false;
};

5.5.2 OnJoinWorldInvitationUpdated 事件

当有人邀请您加入世界大厅或邀请信息已更新时,将触发此事件。通常,接受邀请的玩家在邀请状态标记为"已完成"后会收到 OnWorldBattleSessionUpdated 事件。但是,如果玩家在收到邀请之前已经在指定的战斗会话中,并且已经获得了战斗会话的 IP 和端口(这意味着他已经收到了战斗会话状态为"活跃"的相关事件),那么他可能不会收到 OnWorldBattleSessionUpdated 事件。

struct FClientJoinWorldInvitationUpdatedEvt
{
/** The join world invitation detail information. */
FClientJoinWorldInvitationInfo info;
};

struct FClientJoinWorldInvitationInfo
{
/** The world configuration name associated with the invitation ticket. */
FString world_config;
/** The player who initiated the invitation. */
FPlayerInfo inviter;
/** The ticket of the join world invitation. */
FString invitation_ticket;
/** The status of the Join World invitation. */
EClientJoinWorldInvitationStatus status = EClientJoinWorldInvitationStatus::Dummy;
/** The error message when the status is 'Error'. */
FString error_msg;
/** The current player's action to this join world invitation. */
EClientJoinWorldInvitationAction my_action_to_invitation = EClientJoinWorldInvitationAction::Undecided;
/** Players who have accepted the invitation. (including the inviter) */
TArray<FPlayerInfo> accepted_players;
/** Players who have rejected the invitation. */
TArray<FPlayerInfo> rejected_players;
/** Players who have not yet made a decision. */
TArray<FPlayerInfo> undecided_players;
/** The Unix timestamp(in seconds) when the invitation was created. */
int64 invitation_created_time = 0;
/**
* The maximum time(in seconds) to wait for all invited players to accept/reject.
* After the time (invitation_created_time+invitation_timeout), the event status will be updated to 'Completed'.
*/
int32 invitation_timeout = 0;
/**
* The world battle session joined by the players who accepted the invitation.
* It will be empty until the 'status' changes to 'JoinWorldInvitationStatus::Completed'.
*/
FString battle_session_id;
};

5.5.3 OnWorldBattlePropertiesUpdated 事件

当 DS 更新战斗属性时,该事件将在世界大厅战斗会话中推送至玩家游戏客户端。

struct FClientWorldBattlePropertiesUpdatedEvt
{
/** World config name. */
FString configuration_name;
/** World battle session id. */
FString battle_session_id;
/** */
TArray<FBattleProperty> battle_properties;
};

5.5.4 OnLeaveWorldInvitationUpdated 事件

当有人邀请您离开世界大厅或邀请信息已更新时,将触发此事件。通常,接受邀请的玩家在邀请状态标记为"已完成"后,会收到 OnWorldBattleSessionUpdated 事件。但是,如果玩家在收到邀请之前已经在指定的战斗会话中,并且已经获得了战斗会话的 IP 和端口(这意味着他已经收到了战斗会话状态为"活跃"的相关事件),那么他可能不会收到 OnWorldBattleSessionUpdated 事件。

struct PGOSSDKCPP_API FClientLeaveWorldInvitationUpdatedEvt
{
/** The leave world invitation detail information. */
FClientLeaveWorldInvitationInfo info;
};

/** The leave world invitation detail information. */
struct PGOSSDKCPP_API FClientLeaveWorldInvitationInfo
{
/** The world configuration name associated with the invitation ticket. */
FString world_config;
/** The player who initiated the invitation. */
FPlayerInfo inviter;
/** The ticket of the leave world invitation. */
FString invitation_ticket;
/** The status of the leave World invitation. */
EClientLeaveWorldInvitationStatus status = EClientLeaveWorldInvitationStatus::Dummy;
/** The error message when the status is 'Error'. */
FString error_msg;
/**
* The current player's action to this leave world invitation.
* Call 'AcceptLeaveWorldInvitation' API to accept the invitation and will receive the 'OnLeaveWorldInvitationUpdated' event until the status is 'Completed'.
* Alternatively, call the 'RejectLeaveWorldInvitation' API to reject the invitation and stop receiving the 'OnLeaveWorldInvitationUpdated' event for this 'invitation_ticket'.
* Note: Once you have made your choice, you cannot change your decision.
*/
EClientLeaveWorldInvitationAction my_action_to_invitation = EClientLeaveWorldInvitationAction::Undecided;
/** Players who have accepted the invitation. (including the inviter) */
TArray<FPlayerInfo> accepted_players;
/** Players who have rejected the invitation. */
TArray<FPlayerInfo> rejected_players;
/** Players who have not yet made a decision. */
TArray<FPlayerInfo> undecided_players;
/** The Unix timestamp(in seconds) when the invitation was created. */
int64 invitation_created_time = 0;
/**
* The maximum time(in seconds) to wait for all invited players to accept/reject.
* After the time (invitation_created_time+invitation_timeout), the event status will be updated to 'Completed'.
*/
int32 invitation_timeout = 0;
};

6. 将世界集成到你的游戏服务器中

6.1 准备

在阅读本节之前,请先阅读 DS 托管集成。这将帮助您了解在游戏服务器端管理战斗会话的基础知识。

6.2 世界大厅服务中的战斗会话下面介绍世界大厅服务为游戏服务器生成的战斗会话的关键字段:

  • battle_properties: 战斗会话的自定义KV对。在世界大厅配置中预设,可以通过调用SetBattleProperties接口进行更新。
  • bucket_path:当前世界大厅 战斗会话的Bucket路径。这是从世界大厅过滤器属性名称到属性值的映射。创建战斗会话后,Bucket路径无法修改。
  • configuration_name: 战斗会话所属世界大厅的配置名称。
  • max_players: 战斗会话中的最大玩家数量。该字段的值是可变的,当它发生变化时会触发OnBattleSessionUpdated 事件 。
  • total_players: 战斗会话中的玩家总数。 战斗会话状态为COMPLETED的玩家将被排除在外。

6.3 追踪战斗会话中的玩家

本节介绍游戏服务器如何在战斗会话中追踪玩家的变化(加入和离开)。

6.3.1 处理新玩家加入

每当世界大厅服务将新玩家添加到战斗会话时,都会触发 PGOS Server SDK 的 OnBattleSessionUpdated 事件 。游戏服务器可以监听此事件来确定战斗会话是否已满,或者在玩家的游戏客户端连接之前提前为玩家分配游戏资源。该事件的关键字段如下: - update_reason:在由世界大厅服务生成的战斗会话中,此字段的值应为 WORLD_ADDED_PLAYERS

  • new_player_battle_sessions:新玩家添加到战斗会话 。
  • max_players: 战斗会话中的最大玩家数量。此字段的值是可变的,当它改变时将触发 OnBattleSessionUpdated 事件 。
  • total_players: 战斗会话中的玩家总数。只有战斗会话状态为 COMPLETED 的玩家才会被排除。
    Caution 注意此事件与游戏客户端的JoinWorld调用是异步的,理论上存在玩家的连接请求先于此事件到达的情况,因此请注意不要将此接口的数据作为是否接受玩家连接的依据,我们提供了AcceptPlayerBattleSession接口来处理这种情况。

####6.3.2 处理玩家离开 一般来说,玩家离开某个世界大厅或者世界大厅 战斗会话方式有两种:

  1. 移除游戏服务器中的玩家:游戏服务器可以通过调用 RemovePlayerBattleSession 接口主动将玩家移除战斗会话 ,该动作会触发 PGOS 客户端 SDK 中的 OnWorldBattleSessionUpdated 事件 ,此时玩家战斗会话状态会填充为 Completed
  2. 游戏客户端发起的离开:当游戏客户端主动调用 LeaveWorldAcceptJoinWorldInvitationAcceptLeaveWorldInvitation 等接口时,玩家会被移除出当前世界大厅 战斗会话 ,此时 PGOS 服务端 SDK 中会触发 OnPlayerBattleSessionsTerminated 事件 。
    提示 我们建议游戏服务器对长时间未登录游戏服务器的玩家调用RemovePlayerBattleSession,以防止战斗会话变为幽灵(充满几乎不会再次连接的玩家)并浪费资源。

6.3.3 查询战斗会话中的玩家

使用 DescribePlayerBattleSessions 接口枚举战斗会话中的玩家。

提示 这是一个多用途接口:
  1. 通过填充 DescribePlayerBattleSessionsParams::player_battle_session_id 字段来查询单个玩家的会话信息。

  2. 通过将 DescribePlayerBattleSessionsParams::player_battle_session_id 字段留空来枚举战斗会话中的所有玩家。

查询单个玩家的会话信息

#include "PgosSDKCpp/Public/PgosSDKCpp.h"
#include "PgosSDKCpp/Public/Core/PgosErrorCode.h"

void SomeUObjectClass::DescribeSinglePlayerBattleSession()
{
auto hosting = IPgosSDKCpp::Get().GetServerHostingAPI();
FDescribePlayerBattleSessionsParams params;
params.battle_session_id = "some_battle_session_id";
params.player_battle_session_id = "some_player_battle_session_id";
if (world)
{
world->DescribePlayerBattleSessions(params,
[](const FPgosResult& Ret, const DescribePlayerBattleSessionsResult* Response) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("DescribePlayerBattleSessions Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("DescribePlayerBattleSessions Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

枚举战斗会话中的所有玩家

#include "PgosSDKCpp/Public/PgosSDKCpp.h"
#include "PgosSDKCpp/Public/Core/PgosErrorCode.h"

void SomeUObjectClass::DescribePlayerBattleSessions()
{
auto hosting = IPgosSDKCpp::Get().GetServerHostingAPI();
FDescribePlayerBattleSessionsParams params;
params.battle_session_id = "some_battle_session_id";
params.offset = 0;
params.count = 10;
if (world)
{
world->DescribePlayerBattleSessions(params,
[](const FPgosResult& Ret, const DescribePlayerBattleSessionsResult* Response) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("DescribePlayerBattleSessions Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("DescribePlayerBattleSessions Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

6.3.4 处理待定玩家战斗会话

在以下两种情况下,新玩家可能会以"待定"状态加入战斗会话:

  1. 请求的世界大厅战斗会话被 DS 锁定。
  2. 世界大厅配置中的"DS 审批"属性设置为"所有会话加入请求都需要审批"。 "待定"玩家战斗会话需要经过 DS 审批,否则无法通过 AcceptPlayerBattleSession 接口接受。DS 应调用 ReservedPlayerBattleSession 来审批"待定"玩家战斗会话。"待定"玩家战斗会话仍然会占用世界大厅会话中的一个位置,因此及时调用 RemovePlayerBattleSession 来明确拒绝"待定"玩家战斗会话非常重要。

玩家战斗会话的状态机如下图所示:

stateDiagram-v2 state if_arrrove <<choice>> [*] --> if_arrrove: A new player join battle sesison if_arrrove -->Pending: Need approve Pending-->Reserved: [Approve] a Pending player by calling \nReservePlayerBattleSession Pending-->Completed: [Reject] a Pending player by calling \nRemovePlayerBattleSession if_arrrove --> Reserved: Not need approve Reserved-->Active: AcceptPlayerBattleSession Active-->Completed: RemovePlayerBattleSession

6.4 更新战斗属性

使用 SetBattleProperties 接口更新战斗会话属性。 此操作将在 PGOS 客户端 SDK 中触发 OnWorldBattlePropertiesUpdated 事件。请在您的游戏客户端中订阅此事件,以便及时接收最新的战斗属性。

提示

此接口用于全量更新战斗属性数据。

#include "PgosSDKCpp/Public/PgosSDKCpp.h"
#include "PgosSDKCpp/Public/Core/PgosErrorCode.h"

void SomeUObjectClass::SetBattleProperties()
{
auto hosting = IPgosSDKCpp::Get().GetServerHostingAPI();
FSetBattlePropertiesParams params;
params.battle_session_id = "some_battle_session_id";
params.battle_properties = [];
if (world)
{
world->SetBattleProperties(params,
[](const FPgosResult& Ret, const SetBattlePropertiesResult* Response) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("SetBattleProperties Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("SetBattleProperties Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

6.5 锁定/解锁战斗会话

使用 LockWorldBattleSession 接口来锁定世界大厅战斗会话。当战斗会话被锁定时,PGOS 将无法添加新玩家。

#include "PgosSDKCpp/Public/PgosSDKCpp.h"
#include "PgosSDKCpp/Public/Core/PgosErrorCode.h"

void SomeUObjectClass::LockWorldBattleSession()
{
auto hosting = IPgosSDKCpp::Get().GetServerHostingAPI();
FLockWorldBattleSessionParams params;
params.battle_session_id = "some_battle_session_id";
if (world)
{
world->LockWorldBattleSession(params,
[](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("LockWorldBattleSession Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("LockWorldBattleSession Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

使用UnlockWorldBattleSession接口来解锁世界大厅战斗会话。战斗会话解锁后可以添加新玩家。

#include "PgosSDKCpp/Public/PgosSDKCpp.h"
#include "PgosSDKCpp/Public/Core/PgosErrorCode.h"

void SomeUObjectClass::UnlockWorldBattleSession()
{
auto hosting = IPgosSDKCpp::Get().GetServerHostingAPI();
FUnlockWorldBattleSessionParams params;
params.battle_session_id = "some_battle_session_id";
if (world)
{
world->UnlockWorldBattleSession(params,
[](const FPgosResult& Ret) {
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("UnlockWorldBattleSession Success"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("UnlockWorldBattleSession Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg);
}
});
}
}

6.6 战斗会话清理

在世界大厅服务中生成的战斗会话通常是持久性的,玩家可以不断加入和离开。但是游戏服务器仍然需要及时监控和清理长时间闲置的战斗会话。因为运行着战斗会话的 CVM 不会被缩容,这可能会导致资源浪费。 您可以通过以下两种方式解决这个问题:

  1. 通过调整配置项 World Session Recycling Time 来启用 PGOS 自动完成闲置战斗会话的回收。
  2. DS 通过调用 ProcessEnding 接口在适当的时候主动结束战斗会话。

7. 检查世界大厅运行状态

7.1 检查运行时世界大厅分桶

打开 Battle/World 中的 Buckets 标签页,可以查看当前游戏区服的所有运行时世界大厅分桶。

  • Bucket Path / Bucket Values: 每个分桶的唯一标识符,由筛选属性的值组成。
  • Player Count: 当前分配到该分桶的玩家数量。
  • Battle Session Count: 当前在该分桶中运行的战斗会话数量。

7. Check the Operation of World

7.1 Check Runtime World Buckets

Open the Buckets tab in Battle/World to view all runtime world buckets for the current title region.

  • Bucket Path / Bucket Values: A unique identifier for each bucket, which is composed of the values of the filter attributes.
  • Player Count: The number of players currently assigned to this bucket.
  • Battle Session Count: The number of battle sessions currently running in this bucket.

image-20231121102440134

7.2 检查战斗会话

打开"战斗/战斗会话"页面以查看战斗会话日志。此处包含当前游戏区服中创建的所有战斗会话。

image-20231121103021799

  • 点击战斗会话ID查看详细信息:
    • Player Battle Session(玩家战斗会话):战斗中所有当前玩家战斗会话的列表。如果一个玩家多次加入此战斗会话,列表中将仅包含该玩家最后一次加入的会话信息。
    • Player In-out(玩家进出):玩家战斗会话状态的变更日志。
    • World Bucket Path(世界大厅存储路径):战斗会话的世界大厅存储位置。

image-20231121111313750

8. 关键错误处理

这些是主要的错误,但并非全部。对于未在列表中的错误,错误信息应该已经说明了问题所在。如果您遇到含糊不清的错误信息或任何其他严重错误,请与我们联系。

Error Code相关 API处理建议
kSdkInvalidParameterInviteJoinWorld
InviteJoinWorldBattleSession
AcceptJoinWorldInvitation
RejectJoinWorldInvitation
输入参数值无效,详情请参考错误信息
kBackendDSMInvalidAttributesJoinWorld
InviteJoinWorld
Join请求中填充了非法的filter_attributes。此错误无法通过重试来恢复。
kBackendDSMInvalidWorldConfigJoinWorld
InviteJoinWorld
请求加入世界大厅时,指定的世界大厅配置不存在。
kBackendDSMWorldSessionPlacementFailedJoinWorld
InviteJoinWorld
在请求加入世界大厅时,无法放置新创建的战斗会话。通常可以通过重试来解决。
kBackendDSMWorldSessionAddPlayerFailedJoinWorld
InviteJoinWorld
请求加入世界大厅时无法加入现有战斗会话。通常可以通过重试来解决。
kBackendDSMBucketPathExceedLimitJoinWorld
InviteJoinWorld
请求加入世界大厅时BucketPath数量已超出限制。当出现此错误时,请检查BucketPath是否被误用。
kBackendDSMPlayerCountExceedLimitJoinWorld
InviteJoinWorld
世界大厅中的玩家加入请求已超过WorldConfig中单个战斗会话的最大人数限制。
kBackendDSMWorldSessionFullJoinWorld
InviteJoinWorld
世界大厅人数已满或没有足够的空位供玩家加入。
kBackendDSMWorldSessionIsLockedJoinWorldBattleSession
InviteJoinWorldBattleSession
世界大厅会话已锁定,无法加入。
kBackendDSMWorldConfigMismatchJoinWorldBattleSession
InviteJoinWorldBattleSession
战斗会话不属于指定的世界大厅配置。
kBackendDSMWorldPlayerInvitationConflictedInviteJoinWorld
InviteJoinWorldBattleSession
邀请列表中的玩家已在其他邀请流程中
kBackendDSMWorldPlayerIDsDuplicatedInviteJoinWorld
InviteJoinWorldBattleSession
邀请列表中的玩家已在其他邀请流程中
kBackendDSMWorldPlayerIDsIllegalInviteJoinWorld
InviteJoinWorldBattleSession
邀请列表中的玩家不合法。
kBackendDSMLeaveWorldSessionFailedLeaveWorld退出世界大厅失败。通常可以通过重试来解决。
kSdkNetworkErrorAll Network API网络错误,请查看错误信息了解详情。