将 PlayFab Party 与 MPSD 结合使用
Xbox 多人游戏场景依赖于多人游戏会话目录(MPSD)服务和 MPSD 文档结合使用。 MPSD 文档充当当前游戏会话的名单并驱动多人游戏体验,例如匹配、平台邀请、最近玩家列表以及正在加入。
在本文档中,我们将介绍如何将 PlayFab Party 合并到需要 MPSD 的常见多人游戏流程中。
本文档不提供 MPSD 及其所有功能的深入讨论。 有关详细信息,请参阅 MPSD 文档。
匹配
以下是介绍如何将匹配和 MPSD 与 PlayFab Party 结合使用的简化流程:
玩家将创建并聚集到 MPSD 会话,这些会话代表他们希望跨匹配会话一同畅玩的组。 玩家将使用 Xbox 的邀请和加入功能聚集到这些会话。
这些玩家组将向匹配服务提交票证,该服务会将兼容的玩家组聚集到匹配会话。 此匹配会话本身将由玩家随后将加入的新会话文档表示。 此外,玩家还必须侦听对此会话文档的更改。
匹配会话完成且名单锁定后,游戏必须选择匹配会话的一位成员以设置 PlayFab Party 网络。 选择 Party 网络创建者的简单策略是使用匹配 MPSD 会话文档的第一个成员。
所选成员将使用初始
PartyInvitation
创建网络,将网络访问权限限制为仅匹配会话的成员。 网络成功完成创建后,所选成员应将生成的网络描述符和 Party 邀请作为会话属性发布到会话文档,以供其他成员使用。void OnMatchmakingSessionFinalized( uint32_t usersInSessionCount, const uint64_t* usersInSession ) { PartyInvitationConfiguration initialInvite{}; initialInvite.identifier = nullptr; // let Party select the invitation identifier for simplicity initialInvite.revocability = PartyInvitationRevocability::Anyone; // must be revocable by anyone // the updated invite should contain all users in the matchmaking session std::vector<PartyString> entityIdsInSession; for (uint32_t i = 0; i < usersInSessionCount; ++i) { uint64_t xboxUserId = usersInSession[i]; // Call title-defined xuid->entityid mapping helper PartyString xboxUserEntityId = GetEntityIdFromXboxUserId(xboxUserId); if (xboxUserEntityId != nullptr) { entityIdsInSession.push_back(xboxUserEntityId); } else { DEBUGLOG("User %llu did not have a matching entity ID.", xboxUserId); } } initialInvite.entityIdCount = entityIdsInSession.size(); initialInvite.entityIds = entityIdsInSession.data(); // This is an asynchronous call. It will be completed when StartProcessingStateChanges generates a // PartyCreateNewNetworkCompletedStateChange struct PartyError error = PartyManager::GetSingleton().CreateNewNetwork( m_localPartyUser, &networkConfiguration, 0, nullptr, &initialInvite, nullptr, nullptr, nullptr); if (FAILED(error)) { DEBUGLOG("PartyManager::CreateNetwork failed! 0x%08x\n", error); return; } } void HandleCreateNewNetworkCompleted( const PartyCreateNewNetworkCompletedStateChange& createNewNetworkCompletedStateChange ) { if (createNewNetworkCompletedStateChange.result == PartyStateChangeResult::Succeeded) { // The network was created successfully! Post the networks descriptor and invitation char serializedDescriptor[c_maxSerializedNetworkDescriptorStringLength + 1]; PartyError error = PartyManager::SerializeNetworkDescriptor( &createNewNetworkCompletedStateChange.networkDescriptor, serializedDescriptor); if (PARTY_FAILED(error)) { DEBUGLOG("PartyManager::SerializeNetworkDescriptor failed: 0x%08x\n", error); return; } UpdateSessionProperty( "PartyNetworkDescriptor", // arbitrary property name serializedDescriptor); UpdateSessionProperty( "PartyInitialInvitation", // arbitrary property name createNewNetworkCompletedStateChange.appliedInitialInvitationIdentifier); } else { // The network was not created successfully. // Please refer to CreateNewNetwork reference documentation for retry guidance } }
当每个成员看到会话文档更新时,他们可以使用网络描述符和邀请连接并加入网络。
void OnNetworkInformationPostedToSessionDocument( PartyString serializedNetworkDescriptor, PartyString invitationId ) { PartyNetworkDescriptor networkDescriptor; PartyError error = PartyManager::DeserializeNetworkDescriptor(serializedNetworkDescriptor, &networkDescriptor); if (PARTY_FAILED(error)) { DEBUGLOG("PartyManager::DeserializeNetworkDescriptor failed: 0x%08x\n", error); return; } // attempt to connect to the network PartyNetwork* network; error = PartyManager::GetSingleton().ConnectToNetwork( &networkDescriptor, nullptr, &network); if (PARTY_FAILED(error)) { DEBUGLOG("PartyManager::ConnectToNetwork failed: 0x%08x\n", error); return; } // immediately queue an authentication on the network we've attempted to connect to. error = network->AuthenticateLocalUser( m_localUser, invitationId, nullptr); if (PARTY_FAILED(error)) { DEBUGLOG("PartyNetwork::AuthenticateLocalUser failed: 0x%08x\n", error); return; } }
注意
此处我们提供了将匹配和 MPSD 与 PlayFab Party 合并的流程。 此流程的核心理念可以扩展到你可能对 MPSD 感兴趣的其他流程,但本文档不提供所有可能的流程。 有关详细信息,请参阅 完整的 MPSD 文档。
平台邀请
以下是如何将 Xbox 平台邀请合并到 PlayFab Party 的流程:
PlayerA 创建 MPSD 会话文档、侦听会话更改并创建 Party 网络。 完成 Party 网络创建后,*PlayerA * 会将网络描述符和初始邀请(如有必要)发布到 MPSD 会话文档。
void OnSessionDocumentCreated() { // This is an asynchronous call. It will be completed when StartProcessingStateChanges generates a // PartyCreateNewNetworkCompletedStateChange struct PartyError error = PartyManager::GetSingleton().CreateNewNetwork( m_localPartyUser, &networkConfiguration, 0, nullptr, nullptr, nullptr, nullptr, nullptr); if (FAILED(error)) { DEBUGLOG("PartyManager::CreateNetwork failed! 0x%08x\n", error); return; } } void HandleCreateNewNetworkCompleted( const PartyCreateNewNetworkCompletedStateChange& createNewNetworkCompletedStateChange ) { if (createNewNetworkCompletedStateChange.result == PartyStateChangeResult::Succeeded) { // The network was created successfully! Post the networks descriptor and invitation char serializedDescriptor[c_maxSerializedNetworkDescriptorStringLength + 1]; PartyError error = PartyManager::SerializeNetworkDescriptor( &createNewNetworkCompletedStateChange.networkDescriptor, serializedDescriptor); if (PARTY_FAILED(error)) { DEBUGLOG("PartyManager::SerializeNetworkDescriptor failed: 0x%08x\n", error); return; } UpdateSessionProperty( "PartyNetworkDescriptor", // arbitrary property name serializedDescriptor); } else { // The network was not created successfully. // Please refer to CreateNewNetwork reference documentation for retry guidance } }
当 PlayerA 希望邀请 PlayerB 加入 Party 网络时,PlayerA 会通过游戏内或控制台 UI 向 PlayerB 发起平台邀请。
PlayerB 收到平台邀请,其中包括 PlayerB 可用于查找 PlayerA MPSD 会话文档的“邀请句柄”。
PlayerB 加入会话文档并侦听更改。
PlayerA 看到 PlayerB 加入会话文档。 PlayerA 新建邀请以供 PlayerB 使用,并将该邀请发布到会话文档
void OnUserJoinedSessionDocument( PartyNetwork* network, uint64_t newSessionMemberXboxUserId ) { std::string newMemberIdString = std::to_string(newSessionMemberXboxUserId); // Specify our own invitation id so we don't have to query for it after the invitation has been created. // Here we will specify the invite id with the format "InviterXboxUserID_InviteeXboxUserID" so that we can // ensure this invitation ID doesn't clash with the invitations other members might try and create for this user. std::string invitationId = std::to_string(m_localXboxUserId) + "_" + newMemberIdString; PartyInvitationConfiguration newInvite{}; newInvite.identifier = invitationId.c_str(); newInvite.revocability = PartyInvitationRevocability::Creator; // must be revocable by the creator only // Call title-defined xuid->entityid mapping helper PartyString newSessionMemberEntityId = GetEntityIdFromXboxUserId(newSessionMemberXboxUserId); newInvite.entityIdCount = 1; newInvite.entityIds = &newSessionMemberEntityId; // Create a new invitation which includes all of the users currently in the document PartyInvitation* newInvitation; PartyError error = network->CreateInvitation( m_localUser, &newInvite, nullptr, &newInvitation); if (PARTY_FAILED(error)) { DEBUGLOG("PartyNetwork(0x%p)::CreateInvitation failed! (error=0x%x)", network, error); return; } // Post the invitation to the local user's member property store in the session document, key'd by the invitee's // xbox user id. This will let the invitee recognize when an invitation is intended for them. UpdateMemberProperty( newMemberIdString.c_str(), invitationId.c_str()); }
PlayerB 看到发布到会话文档的邀请,并使用邀请加入 Party 网络。
void OnRemoteMemberPropertyUpdated( PartyString memberPropertyKey, PartyString memberPropertyValue ) { // The member property update signifies a new invitation, if the remote member updated a property that matches // our xbox user id. if (memberPropertyKey == std::to_string(m_localXboxUserId)) { OnUserInvitationPostedToSessionDocument(memberPropertyValue); } // ... } void OnUserInvitationPostedToSessionDocument( PartyString invitationId ) { // The network descriptor should have already been posted to the session document before the invitation. // Call title-defined function to pull it from the session document. PartyNetworkDescriptor networkDescriptor = QueryNetworkDescriptorFromSessionDocument(); // attempt to connect to the network PartyNetwork* network; error = PartyManager::GetSingleton().ConnectToNetwork( &networkDescriptor, nullptr, &network); if (PARTY_FAILED(error)) { DEBUGLOG("PartyManager::ConnectToNetwork failed: 0x%08x\n", error); return; } // immediately queue an authentication on the network we've attempted to connect to. error = network->AuthenticateLocalUser( m_localUser, invitationId, nullptr); if (PARTY_FAILED(error)) { DEBUGLOG("PartyNetwork::AuthenticateLocalUser failed: 0x%08x\n", error); return; } }
重要
如果创建邀请的 PartyLocalUser 离开网络,则通过 PartyNetwork::CreateInvitation 创建的邀请将失效。 因此,如果新用户将自己添加到会话文档,但邀请他们的用户已离开,则我们建议新用户将自己从会话文档中删除,并等待另一用户重新邀请。
正在加入
正在加入游戏会话非常类似于 平台邀请 场景。 核心区别在于,PlayerA 不会向 PlayerB 发送“邀请句柄”,而是 PlayerB 将在从平台 UI 发起正在加入时获得“加入句柄”。 使用此“加入句柄”,PlayerB 将加入会话文档并侦听更改。 PlayerA 将做出响应,为他们新建 Party 邀请并将其发布到会话文档。 PlayerB 将在网络描述符旁看到此新邀请,并使用该邀请加入 Party 网络。
重要
如果创建邀请的 PartyLocalUser 离开网络,则通过 PartyNetwork::CreateInvitation 创建的邀请将失效。 因此,如果新用户收到来自正在加入流程的 Party 邀请,但该邀请却由于创建它的用户已离开而无法使用,则我们建议新用户将自己从会话文档中删除,并稍后重新加入。 这将允许会话的另一成员重启流程并为此用户生成新的 Party 邀请。
断开连接和清理
如果玩家离开或以其他方式与 Party 网络断开连接,他们还应将自己从与该 Party 网络关联的 MPSD 会话中删除。 不是由 PartyNetwork::LeaveNetwork 操作发起的 Party 网络断开连接被视为严重错误。 在遇到严重的断开连接后,玩家可能会尝试重新连接并重新验证到网络,但还必须重新加入 MPSD 会话。
如果玩家与 MPSD 会话的连接暂时中断,则他们可能会与该会话断开连接。 玩家可以尝试重新加入会话,但如果失败,则应调用 PartyNetwork::LeaveNetwork,自愿将自己从 Party 网络中删除。
注意
检测 Party 网络和 MPSD 会话断开连接的机制和启发法并不相同。 即使在玩家将同时与 Party 网络和 MPSD 会话断开连接的场景中,这些断开连接事件也为独立事件,且无法保证它们发生的时间互相接近。 游戏应处理玩家可能只与 Party 网络或 MPSD 会话断开连接的场景。
如果游戏关闭,则玩家将自动与 Party 网络和 MPSD 文档断开连接,无需进一步清理。