跳到主要内容

P2P 对战 (beta)

概述

PGOS 提供以下功能,帮助游戏开发者构建 P2P 游戏:

  1. P2P 匹配
  2. P2P 对战会话
  3. P2P 连接
  4. P2P 网络驱动程序

1. P2P 匹配

1.1 设置 P2P 战斗会话的匹配配置

首先,您需要拥有一个 P2P Placer,并在任何匹配配置中将其设置为“关联Placer”。配置完成后,通过此匹配配置匹配的玩家将被分配到 P2P 战斗会话,并经历与标准在线战斗会话不同的匹配流程。

  • 有关Placer管理,请参阅 Placer 文档。
  • 有关匹配配置的详细信息,请参阅 Placer 文档。

image-20250317153505924

1.2 P2P 匹配流程

sequenceDiagram participant A as PlayerA participant P as P2P Server participant M as P2P Match Server participant S as P2P Battle Server participant B as PlayerB rect rgb(170, 190, 253) note over A,B: Nat type detecting must be done before any P2P matchmaking request !! A->>P:call DetectNatType B->>P:call DetectNatType end rect rgb(170, 190, 253) note over A,B: Match server searching for players A->>M:StartP2PMatchmaking M->>M:Searching for players B->>M:StartP2PMatchmaking M->>M:Two players found, choose playerA as HOST M-->>A:OnMatchmakingProcessChanged(HOST=PlayerA, BattleSessionId=xxx) M-->>B:OnMatchmakingProcessChanged(HOST=PlayerA, BattleSessionId=xxx) end rect rgb(170, 190, 253) note over A,B: P2P battle session placement M->>S:Place a P2P battle session(HOST=PlayerA) S-->>A:Trigger OnStartP2PBattleSession enent end
  • NAT 类型检测:请注意,NAT 类型检测必须启动 P2P 匹配之前完成,否则 StartP2PMatchmaking 接口将立即返回失败。
  • 启动 P2P 匹配:游戏客户端调用 StartP2PMatchmaking 接口触发匹配。请确保匹配配置符合 2.1 节中定义的设置;否则,生成的战斗会话将被放置到与在线匹配者关联的舰队中,而不是主机游戏客户端。匹配结果将通过 OnMatchmakingProcessChanged 事件通知所有参与者,该事件包含主机玩家的 ID。
  • P2P 战斗会话放置:匹配成功后,P2P 匹配服务器将请求 P2P 战斗服务器放置战斗会话。该会话最终通过 OnStartP2PBattleSession 事件分配给主机客户端。此工作流程将在下一节中详细介绍。
备注

以下是专业的技术翻译:

在 P2P 对战中选择主机玩家时,我们遵循以下原则:

  1. 我们根据 NAT 类型对匹配的玩家进行排序
  2. 从开放类型开始,我们优先选择 NAT 类型更合适的玩家作为主机
  3. 无论 NAT 类型如何,主机都会被选中,即使是 SymmetricUDPFirewall 或 UDPBlocked

以下是 PGOS 对 NAT 类型的定义,您可以在 PGOS 客户端 SDK 中找到:

enum class NatType : uint8_t {
Unknown = 0,
/** No Nat */
Open = 1,
/** Full Cone */
FullCone = 2,
/** Address restricted cone */
AddressRestrictedCone = 3,
/** Port restricted cone */
PortRestrictedCone = 4,
/** Symmetric */
Symmetric = 5,
/** No NAT with UDP firewall */
SymmetricUDPFirewall = 6,
UDPBlocked = 7,
};

2. P2P 战斗会话

与在线战斗会话不同,PGOS 通过游戏客户端而非DS或 Local DS管理 P2P 战斗会话。目前,PGOS 用户只能通过 P2P 匹配服务创建 P2P 战斗会话,供主机和连接的客户端使用。本章介绍 P2P 战斗会话的会话管理功能及其相关数据。

2.1 P2P 战斗会话流程

sequenceDiagram participant H as Host Game Client participant C as Game Client participant M as P2P Match Server participant S as P2P Battle Server rect rgb(170, 190, 253) note over H,S: Place a P2P battle session on Host game client M->>S: Place P2P battle session S-->>H: OnStartP2PBattleSession S-->>C: OnP2PBattleSessionUpdated(status=Placing) end H->>H: Prepare for the game rect rgb(170, 190, 253) note over H,S: Host game client active a P2P battle session H->>S: ActivateP2PBattleSession S-->>H: OnP2PBattleSessionUpdated(status=Active) S-->>C: OnP2PBattleSessionUpdated(status=Active) end rect rgb(170, 190, 253) note over H,S: Check for P2P client connection request C->>H: P2P connect to Host game client H->>+S: AcceptP2PPlayerBattleSession S-->>-H: Rsp: accept success or not end H->>H: P2P game ongoing rect rgb(170, 190, 253) note over H,S: Handle player exit C->>H: P2P disconnect from Host game client H->>S: RemoveP2PPlayerBattleSession end rect rgb(170, 190, 253) note over H,S: Host game client ends a P2P battle session H->>S: TerminateP2PBattleSession end

在主机游戏客户端上放置 P2P 对战会话

  • P2P 对战会话通过 PGOS P2P 对战服务器 在主机游戏客户端上放置。主机可以监听 OnStartP2PBattleSession 事件以接收放置请求。

  • 非主机参与者应监听 OnP2PBattleSessionUpdated 事件以获取会话放置的实时更新。

  • PGOS 建议所有 P2P 对战会话参与者订阅这些事件,因为在主机确认之前,所有玩家都将保持对等角色。具体实现细节可能因游戏逻辑而异

在主机游戏客户端上激活 P2P 对战会话

  • 主机游戏客户端必须在适当的时间调用 ActivateP2PBattleSession 接口,将会话状态从 Placing 转换为 Active。参与者将通过 OnStartP2PBattleSession 事件接收状态更新。
  • PGOS 建议仅在主机完成游戏准备并准备好接受 P2P 连接后才激活会话。客户端应在观察到会话状态变为 Active 后与主机建立 P2P 连接。

管理 P2P 对战会话中的玩家

  • PGOS 会为每个参与者分配一个玩家对战会话 ID。主机可以通过调用 AcceptP2PPlayerBattleSession 接口,使用此 ID 验证客户端的身份。
  • 主机可以通过调用 RemoveP2PPlayerBattleSession 来终止玩家的会话。一旦移除,关联的玩家对战会话 ID 将失效,无法用于后续的 AcceptP2PPlayerBattleSession 调用。

终止 P2P 对战会话

  • 游戏结束时,主游戏客户端必须调用 TerminateP2PBattleSession。PGOS 将销毁会话实例,但保留事务日志。这将停止所有 PGOS 管理的操作,包括状态同步和玩家会话管理。
  • 如果主游戏玩家离线(例如,通过 LogoutPGOS、进程崩溃或网络断开连接),则会自动终止
  • 参与者在终止时会收到 OnP2PPlayerBattleSessionsTerminated 事件,并可以立即寻求新的会话。

2.2 生命周期控制

本节详细介绍了 P2P(玩家)战斗会话的状态机。了解这些状态转换对于精确控制会话实例至关重要。

P2P 战斗会话生命周期

stateDiagram [*] --> Placing: Placement from P2P match server Placing --> Active: ActivateP2PBattleSession called by host Placing --> TimedOut: ActivateP2PBattleSession is not called Active --> Terminated: TerminateP2PBattleSession is called by host Active --> Terminated: Host client disconnected from PGOS Placing --> Terminated: Host client disconnected from PGOS Terminated --> [*] TimedOut --> [*]
  • Placing:P2P 对战会话已分配给主机,但等待激活。
  • Active: 会话已由主机激活。游戏客户端在检测到此状态后应尝试连接到主机客户端。
  • TimedOut:如果主机未能在允许的时间内激活会话,PGOS 将自动销毁会话。
  • Terminated:会话将由主机手动终止,或由主机离线时PGOS P2P 对战服务自动终止。

P2P 玩家战斗会话生命周期

stateDiagram [*] --> Reserved: Player being added to a P2P battle session Reserved --> Active: AcceptP2PPlayerBattleSession is called by host Reserved --> Completed: RemoveP2PPlayerBattleSession is called by host Reserved --> Completed: TerminateP2PPlayerBattleSession is called by client Active --> Completed: RemoveP2PPlayerBattleSession is called by host Active --> Completed:TerminateP2PPlayerBattleSession is called by client Completed --> [*]
  • Reserved:会话中为玩家保留一个位置。
  • Active成功调用 AcceptP2PPlayerBattleSession 后转换到此状态。
  • 已完成:玩家战斗会话的终止状态。在此状态下调用 AcceptP2PPlayerBattleSession 将失败。触发器包括:
  • Host-initiated:主机在确认(或要求)玩家断开连接时调用 RemoveP2PPlayerBattleSession
  • Client-initiated:游戏客户端调用 TerminateP2PPlayerBattleSession 主动退出会话。

2.3 主机迁移

与在线对战相比,P2P 对战对网络环境的要求更高,这意味着游戏中的主机可能会遇到问题。这些问题可能包括由于主机断线导致的对战终止,或客户端与主机之间不稳定的延迟,从而对游戏体验造成负面影响。

为了解决这个问题,PGOS 为 P2P 对战对战提供了主机迁移支持。您可以在 P2P Placer中开启“启用主机迁移”选项来启用此功能。

image-20250422160817736

s

PGOS 提供主机迁移工作流支持,当原主机出现异常状态时,会自动从玩家中选择新的主机。如果您希望在主机迁移后从之前的进度继续游戏,则需要在游戏逻辑中实现游戏状态备份和同步功能。

PGOS 不限制主机迁移的次数。每次选择新主机的算法与初始主机选择相同。有关算法详情,请参阅上一篇文章。

我们添加了两个新状态来支持 P2P 战斗会话的主机迁移:

stateDiagram-v2 [*] --> Active: Battle session started Active --> HostHealthCheck: Host failure detected HostHealthCheck --> Terminated: Host terminated session HostHealthCheck --> Active: Players voted to keep current host HostHealthCheck --> NewHostElected: New host selected NewHostElected --> Active: New host activated session NewHostElected --> Terminated: New host rejected Terminated --> [*]
  • HostHealthChecking - 进入此状态后,会话中所有非主机玩家必须在指定时间内通过“ReportHostHealth”接口报告主机健康状况。PGOS 将根据 P2P Placer 中配置的阈值,决定是保留当前主机还是选择新的主机。P2P 战斗会话通过以下任一触发条件进入“HostHealthChecking”状态:
    • 任何对等客户端为活跃的 P2P 战斗会话调用“ReportHostHealth”
    • PGOS 后端检测到主机客户端在线玩家会话中的异常
  • NewHostSelected - 此状态表示成功选出新主机。新主机必须在规定时间内调用“ActivateP2PBattleSession”才能恢复游戏,否则会话将终止。新主机也可以调用“RejectHostMigrating”来中止迁移,这也会终止会话。

所有 P2P 战斗会话状态转换均通过“OnP2PBattleSessionUpdated”事件推送。请密切关注此事件以获取实时会话状态更新。****

2.4 检查 P2P 战斗会话

与在线战斗会话类似,PGOS 会记录 P2P 战斗会话的日志。您可以通过 门户 访问这些日志,以进行监控和审计。

P2P 战斗会话列表**

image-20250317161025945

P2P 战斗绘画详情

image-20250317161209176

玩家 P2P 战斗会话详情

image-20250317161333963

3. P2P 连接

3.1 使用流程

graph TD A[Initialize API] --> B{Need NAT Info?} B -->|Yes| C[Detect NAT Type] B -->|No| D[Initiate P2P Connection] C --> D D --> E{Received Connection Request?} E -->|Yes| F[Accept/Reject Connection] E -->|No| G[Wait] F --> H[Send P2P Message] H --> I[Close Connection]

3.2 初始化 API

// Get API instance
auto P2PConnectionAPI = IPgosSDKCpp::Get().GetClientP2PConnectionAPI();

3.3 检测 NAT 类型

3.3.1 NAT 类型检测的目的

NAT 类型检测的主要目的是为 P2P 匹配提供参数,而非 P2P 连接建立的必要步骤。具体而言:

  1. 匹配优化:了解 NAT 类型有助于匹配后端选择更合适的对等节点,从而提高连接成功率。
  2. 网络诊断:帮助开发者了解用户的网络环境,为后续网络优化提供参考。
// NAT type detection must be performed before initiating P2P matching
P2PConnectionAPI->DetectNatType(FPgosClientOnDetectNatType::CreateLambda(
[](const FPgosResult& Result, const FClientDetectNatTypeResult* Data) {
if (Result.err_code == (int32)Pgos::PgosErrCode::kSuccess) {
// SDK automatically caches the result, no manual handling required
UE_LOG(LogTemp, Display, TEXT("NAT Type detected"));

// After successful detection, P2P matching can be initiated
StartP2PMatchmaking();
} else {
UE_LOG(LogTemp, Error, TEXT("Detect NAT type failed: %s"), *Result.msg);
// Handle detection failure
HandleNatDetectionFailure();
}
}

3.3.2 NAT 类型检测机制

  1. 强制检测
    • 在 NAT 类型检测之前,P2P 匹配会失败。
    • 发起 P2P 匹配之前,必须至少完成一次 NAT 类型检测。
  2. 自动缓存机制
    • 首次调用 DetectNatType 后,SDK 会自动缓存检测结果。
    • 缓存结果有效,直到下次调用 DetectNatType
    • 建议在网络环境发生变化时(例如在 WiFi/4G 之间切换)重新检测。
  3. 匹配集成
    • 匹配模块会自动从缓存中检索缓存的 NAT 类型信息。
    • 开发者无需手动设置或传递 NAT 类型信息。
  4. 错误处理
    • 如果检测失败,提示用户并重试。
    • 建议在检测失败时提供网络诊断建议。

3.4 初始化 P2P 连接

FClientP2PConnectParams Params;
Params.peer_player_id = "target_player_id";
// The SDK does not validate the token; it simply passes it to the peer. The game must validate it on the peer side.
// If used with P2P matching, it is recommended to pass the battle session ID here.
Params.token = "token";
Params.connection_type = EClientP2PConnectionType::Unspecified;
FString ConnectionId = P2PConnectionAPI->P2PConnect(Params, FPgosClientOnP2PConnect::CreateLambda(
[](const FPgosResult& Result, const FClientP2PConnectResult* Data) {
if (Result.err_code == (int32)Pgos::PgosErrCode::kSuccess) {
UE_LOG(LogTemp, Display, TEXT("P2P connection established"));
} else {
UE_LOG(LogTemp, Error, TEXT("P2P connection failed: %s"), *Result.msg);
}
}
));
  1. 注意事项
    1. 每次调用 P2PConnect 都会向对端发送一个新的连接请求。
    2. 如果调用失败,返回的 ConnectionId 将为空。
    3. 用户可以选择 P2P 连接类型:
    4. 未指定:优先使用 P2P 连接,如果 P2P 连接失败,则在短暂延迟后回退到中继服务器连接。
    5. P2P:通过 P2P 连接。
    6. Relay:通过中继服务器连接。
    7. 连接成功后,回调中的 connection_type 字段指示最终的连接类型。
    8. 仅在对端接受请求且连接建立/失败,或对端拒绝请求后,才会执行 P2PConnect 回调。如果对端长时间未处理请求,则会执行错误回调。

3.5 处理 P2P 连接请求

客户端需要监听 OnP2PConnectRequest 事件,并根据具体情况调用 AcceptP2PConnectRejectP2PConnect

P2PConnectionAPI->OnP2PConnectRequest().AddLambda(
[=](const FClientP2PConnectRequestEvt& Event) {
UE_LOG(LogTemp, Display, TEXT("Received P2P connection request from %s"), *Event.peer_player_id);

// Accept connection
P2PConnectionAPI->AcceptP2PConnect(Event.connection_id, FPgosClientOnAcceptP2PConnect::CreateLambda(
[](const FPgosResult& Result, const FClientP2PConnectResult* Data) {
if (Result.err_code == (int32)Pgos::PgosErrCode::kSuccess) {
UE_LOG(LogTemp, Display, TEXT("P2P connection accepted"));
} else {
UE_LOG(LogTemp, Error, TEXT("Accept P2P connection failed: %s"), *Result.msg);
}
}
));
// Reject connection
P2PConnectionAPI->RejectP2PConnect(Event.connection_id);
}
);

注意事项

调用 AcceptP2PConnect 时,仅在连接成功/失败后执行回调。

3.6 发行 P2P 消息

FClientSendP2PMessageParams Params;
Params.connection_id = "connection_id";
Params.message = FPgosUtil::ConvertStringToBytes("Hello, P2P!");

FPgosResult Result = P2PConnectionAPI->SendP2PMessage(Params);
if (Result.err_code == (int32)Pgos::PgosErrCode::kSuccess) {
UE_LOG(LogTemp, Display, TEXT("P2P message sent successfully"));
} else {
UE_LOG(LogTemp, Error, TEXT("Failed to send P2P message: %s"), *Result.msg);
}

注意事项

连接成功后才能发送消息。

3.7 关闭 P2P 连接

FPgosResult Result = P2PConnectionAPI->P2PClose("connection_id");
if (Result.err_code == (int32)Pgos::PgosErrCode::kSuccess) {
UE_LOG(LogTemp, Display, TEXT("P2P connection closed successfully"));
} else {
UE_LOG(LogTemp, Error, TEXT("Failed to close P2P connection: %s"), *Result.msg);
}

注意:

  1. 对未处理的 P2P 请求调用 P2PClose 无效。
  2. AcceptP2PConnect 之后、连接建立之前调用 P2PClose 将直接关闭当前连接。

3.8 获取 P2P 连接信息

连接信息包括:连接状态、RTT 延迟、丢包率等。

3.8.1 获取特定玩家的所有 P2P 连接信息

TArray<FClientP2PConnectionInfo> Connections = P2PConnectionAPI->GetP2PConnectionInfoByPlayerId("target_player_id");
for (const FClientP2PConnectionInfo& Info : Connections) {
UE_LOG(LogTemp, Display, TEXT("Connection ID: %s, RTT: %d"), *Info.connection_id, Info.rtt);
}

3.8.2 获取特定连接ID的P2P连接信息

FClientP2PConnectionInfo ConnectionInfo;
if (P2PConnectionAPI->GetP2PConnectionInfoByConnectionId("connection_id", ConnectionInfo)) {
UE_LOG(LogTemp, Display, TEXT("Connection ID: %s, RTT: %d"), *ConnectionInfo.connection_id, ConnectionInfo.rtt);
} else {
UE_LOG(LogTemp, Warning, TEXT("Invalid connection ID"));
}

3.8.3 获取所有P2P连接信息

TArray<FClientP2PConnectionInfo> AllConnections = P2PConnectionAPI->GetAllP2PConnectionInfo();
for (const FClientP2PConnectionInfo& Info : AllConnections) {
UE_LOG(LogTemp, Display, TEXT("Connection ID: %s, RTT: %d"), *Info.connection_id, Info.rtt);
}

注意:

  1. 返回的连接信息是调用时的一个快照,可能无法反映实时状态。
  2. RTT 延迟、丢包率和其他信息大约每 5 秒更新一次。

3.9 事件监听

// Listening P2P messages
P2PConnectionAPI->OnP2PMessage().AddLambda(
[](const FClientP2PMessageEvt& Event) {
UE_LOG(LogTemp, Display, TEXT("Received P2P message: %s"), *FPgosUtil::ConvertBytesToString(Event.message));
}
);

// Listening P2P connection close events
P2PConnectionAPI->OnP2PConnectionClosed().AddLambda(
[](const FClientP2PConnectionClosedEvt& Event) {
UE_LOG(LogTemp, Display, TEXT("P2P connection closed: %s"), *Event.connection_id);
}
);

4. P2P 网络驱动程序

PGOS P2P 网络驱动程序是一款专为P2P多人游戏设计的虚幻引擎自定义网络解决方案。它能够处理 NAT 遍历和直接对等连接,使玩家无需专用服务器即可发起或加入会话。本文档详细介绍了其公共 API 和使用指南。

P2P 网络驱动程序的底层通信是使用 PgosSDK 中的 P2P 连接 接口实现的。当然,您也可以直接使用 P2P 连接 来实现您自己的 P2P 游戏。

注意:

除了 FPgosP2PConnectionAPI::DetectNatType 接口外,如果您使用 P2P NetDriver,通常不需要使用 P2P Connection 中的任何其他接口。

P2P NetDriver 仍处于实验版本。

4.1 接口介绍

4.1.1 主机初始化 P2P 连接

/**
* Host initializes a P2P connection. Other regular clients can connect only after the Host has been initialized.
*
* @param LocalPlayer Specify the local player that the host should be associated with.
*/
void HostSetupP2PConnection(ULocalPlayer* LocalPlayer);

4.1.2 客户端请求与主机建立P2P连接

/**
* Client request a p2p connection to the host.
*
* @param LocalPlayer Specify the local player that the client should be associated with.
* @param PeerPlayerId Host's player ID.
* @param ResultDelegate The result delegate after the API execution ends, and it will be called in the GAME THREAD.
*/
void ClientP2PConnect(ULocalPlayer* LocalPlayer, const FString& PeerPlayerId, FPgosNetDriverOnP2PConnect ResultDelegate);

4.1.3 关闭到此主机的所有P2P连接

/**
* Close all p2p connections to this host.
*
* @param LocalPlayer Specify the local player that the client should be associated with.
*/
void HostCloseAllP2PConnection(ULocalPlayer* LocalPlayer);

4.2 P2P 对战使用步骤

4.2.1 将 P2P NetDriver 集成到项目中

  • 在 DefaultEngine.ini 文件中,将 PgosNetDriver 配置为 NetDriver,将 PgosConnection 配置为 NetConnection。
[/Script/Engine.Engine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/PgosNetDriver.PgosNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver")

[/Script/PgosNetDriver.PgosNetDriver]
NetConnectionClassName="/Script/PgosNetDriver.PgosConnection"
ConnectionTimeout=80.0
InitialConnectTimeout=120.0
NetServerMaxTickRate=30
MaxNetTickRate=120
KeepAliveTime=0.2
MaxClientRate=100000
MaxInternetClientRate=100000
RelevantTimeout=5.0
SpawnPrioritySeconds=1.0
ServerTravelPause=4.0
  • 依赖于 YourGame.Build.cs 中的“PgosNetDriver”模块。
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay",
"OnlineSubsystem", "OnlineSubsystemUtils", "PgosSDKCpp", "PgosSDKBp", "PgosNetDriver"});

4.2.2 开始 P2P 对战

  • 在开始匹配之前,成功调用一次 FPgosP2PConnectionAPI::DetectNatType 来检查玩家的网络类型。
IPgosSDKCpp::Get(GetOwningLocalPlayer()).GetClientP2PConnectionAPI()->DetectNatType(FPgosClientOnDetectNatType::CreateLambda(
[this](const FPgosResult& Result, const FClientDetectNatTypeResult* Data)
{
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("DetectNatType Success"))
}
else
{
UE_LOG(LogTemp, Log, TEXT("DetectNatType Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg)
}
}));
  • 使用“FPgosMatchmakingAPI::StartP2PMatchmaking”开始 P2P 匹配。
const auto Callback = [this](const FPgosResult& Ret, const FClientStartMatchmakingInfo* Info)
{
if (Ret.err_code == (int32)Pgos::PgosErrCode::kSuccess)
{
UE_LOG(LogTemp, Log, TEXT("StartP2PMatchmaking Success"))
} else
{
UE_LOG(LogTemp, Log, TEXT("StartP2PMatchmaking Failed: err_code=%d, err_msg=%s"), Ret.err_code, *Ret.msg)
}
};

FClientStartMatchmakingParams Params;
Params.configuration_name = TEXT("battle-1v1-Matchmaking-p2p");
Params.player_info_list = MatchmakingPlayerInfos;
IPgosSDKCpp::Get(GetOwningLocalPlayer()).GetClientMatchmakingAPI()->StartP2PMatchmaking(Params, Callback);
  • 匹配成功后,主机会收到 FPgosMatchmakingAPI::OnStartP2PBattleSession 事件。此时,主机可以使用 FPgosNetDriverModule::HostSetupP2PConnection 初始化 P2P 连接,等待其他玩家连接。之后,主机进入对战地图,并通过 FPgosP2PBattleAPI::ActivateP2PBattleSession 激活会话。
void UMatchmakingScreen::NativeConstruct()
{
const auto P2PBattleApi = IPgosSDKCpp::Get(GetOwningLocalPlayer()).GetClientP2PBattleAPI();
P2PBattleApi->OnStartP2PBattleSession().AddUObject(this, &UMatchmakingScreen::OnP2PHostStartBattleSession);
}

void UMatchmakingScreen::OnP2PHostStartBattleSession(const FClientStartP2PBattleSessionEvt& Event)
{
const auto GameInstance = Cast<UPgosShooterGameInstance>(GetGameInstance());
if (GameInstance)
{
GameInstance->HostSaveP2PBattleSession(Event);
}

FPgosNetDriverModule::Get().HostSetupP2PConnection(GetOwningLocalPlayer());
GetOwningLocalPlayer()->GetSubsystem<UGamePlayUtilities>()->P2PHostTravel(Event.battle_session.battle_session_id,
*Event.battle_session.cur_player_battle_session_info.player_battle_session_id);
}
void APGOSBattleGameModeP2P::OnStartBattleSession(const FString& BattleSessionId, FString& ErrorMessage)
{
// ...

if (auto API = GetPgosClientP2PBattleAPI())
{
FClientActivateP2PBattleSessionParams Params;
Params.battle_session_id = CurrentBattleSession.battle_session.battle_session_id;
FPgosOnApiResult ResultDelegate;
ResultDelegate.BindLambda([](const FPgosResult& Result)
{
ULogBlueprintFunctionLibrary::Log(
FString::Printf(TEXT("ActivateP2PBattleSession. code=(%d), msg=(%s)"), Result.err_code, *Result.msg));
});
API->ActivateP2PBattleSession(Params, ResultDelegate);
}

// ...
}
  • 其他玩家会收到 FPgosMatchmakingAPI::OnP2PBattleSessionUpdated 事件。当主机激活会话,且会话状态为 Active 时,其他玩家可以通过 FPgosNetDriverModule::ClientP2PConnect 连接主机。连接成功后,即可进入对战地图。
void UMatchmakingScreen::NativeConstruct()
{
const auto P2PBattleApi = IPgosSDKCpp::Get(GetOwningLocalPlayer()).GetClientP2PBattleAPI();
P2PBattleApi->OnP2PBattleSessionUpdated().AddUObject(this, &UMatchmakingScreen::OnP2PClientBattleSessionUpdated);
}

void UMatchmakingScreen::OnP2PClientBattleSessionUpdated(const FClientP2PBattleSessionUpdatedEvt& Event)
{
if (Event.battle_session.status == EClientP2PBattleSessionStatus::Active)
{
FPgosNetDriverOnP2PConnect Delegate;
Delegate.BindLambda([this, Event](const FPgosResult& Result, const FString& ServerAddr)
{
UE_LOG(LogTemp, Log,
TEXT("OnP2PClientBattleSessionUpdated P2PConnect. errcode=(%d), msg=(%s), ServerAddr=(%s)"),
Result.err_code, *Result.msg, *ServerAddr)
GetOwningLocalPlayer()->GetSubsystem<UGamePlayUtilities>()->P2PClientTravel(ServerAddr,
Event.battle_session.cur_player_battle_session_info.player_battle_session_id);
});
FPgosNetDriverModule::Get().ClientP2PConnect(GetOwningLocalPlayer(), Event.battle_session.host_player_id, Delegate);
}
}
  • 进行战斗。

  • P2P 战斗结束后,主机将通过 FPgosNetDriverModule::HostCloseAllP2PConnection 断开与其他玩家的所有 P2P 连接。

void APGOSBattleGameModeP2P::Logout(AController* Exiting)
{
Super::Logout(Exiting);

// ...

FPgosNetDriverModule::Get().HostCloseAllP2PConnection(LocalPlayer);

// ...

}