> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 将 PlayFab Party 与 MPSD 结合使用

> 将 PlayFab Party 语音和数据网络与 XBOX 多人游戏会话目录 (MPSD) 结合使用，以实现跨网络的会话管理和邀请。

XBOX 多人游戏场景依赖于多人游戏会话目录 (MPSD) 服务和 MPSD 文档的使用。MPSD 文档充当当前游戏会话的名册，并驱动多人游戏体验，例如匹配、平台邀请、最近玩家列表和加入正在进行的游戏。

本文档将介绍如何将 PlayFab Party 融入到需要 MPSD 的常见多人游戏流程中。

本文档不会深入讨论 MPSD 及其所有功能。有关详细信息，请参考 [MPSD 文档](/services/xbox-services/multiplayer/mpsd/live-mpsd-overview)。

## 匹配

以下是关于如何将匹配和 MPSD 与 PlayFab Party 结合使用的简化流程：

1. 玩家将创建并聚集到 MPSD 会话中，这些会话代表他们希望在匹配会话之间一起游戏的组。玩家将通过使用 XBOX 的邀请和加入功能聚集到这些会话中。

2. 这些玩家组将向匹配服务提交票据，匹配服务将把兼容的玩家组聚集到一个匹配会话中。此匹配会话本身将由一个新的会话文档表示，玩家随后会加入该文档。玩家还必须监听此会话文档的更改。

3. 一旦匹配会话确定完成并且名册被锁定，游戏必须从匹配会话的成员中选举一个来设置 PlayFab Party 网络。一个简单的策略是选择匹配 MPSD 会话文档中的第一个成员作为 Party 网络的创建者。

4. 所选成员将使用初始 `PartyInvitation` 创建网络，该邀请仅允许匹配会话的成员访问网络。网络创建成功完成后，所选成员应将生成的网络描述符和 Party 邀请作为会话属性发布到会话文档，供其他成员使用。

   ```cpp theme={null}
   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
       }
   }
   ```

5. 当每个成员看到会话文档已更新时，可以使用网络描述符和邀请连接并加入网络。

   ```cpp theme={null}
   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;
       }
   }
   ```

<Note>
  这里我们展示了一种将匹配和 MPSD 与 PlayFab Party 结合使用的流程。此流程的核心思想可以扩展到你可能感兴趣的其他 MPSD 流程，但展示所有可能的流程超出了本文档的范围。有关更多信息，请参见 [完整的 MPSD 文档](https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/live/get-started/live-xbl-overview)。
</Note>

## 平台邀请

以下是将 XBOX 平台邀请与 PlayFab Party 结合使用的流程：

1. *玩家 A* 创建 MPSD 会话文档、监听会话更改并创建 Party 网络。当 Party 网络创建完成时，*玩家 A* 将网络描述符和初始邀请（如有必要）发布到 MPSD 会话文档。

   ```cpp theme={null}
   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
       }
   }
   ```

2. 当 *玩家 A* 想要邀请 *玩家 B* 加入 Party 网络时，*玩家 A* 通过游戏内或主机 UI 向 *玩家 B* 发起平台邀请。

3. *玩家 B* 收到平台邀请，其中包含一个"邀请句柄"，*玩家 B* 可以使用它来找到 *玩家 A* 的 MPSD 会话文档。

4. *玩家 B* 加入会话文档并监听更改。

5. *玩家 A* 看到 *玩家 B* 加入了会话文档。*玩家 A* 为 *玩家 B* 创建一个新的邀请，并将该邀请发布到会话文档。

   ```cpp theme={null}
   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());
   }
   ```

6. *玩家 B* 看到发布到会话文档的邀请，并使用它加入 Party 网络。

   ```cpp theme={null}
   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;
       }
   }
   ```

<Info>
  如果创建邀请的 PartyLocalUser 离开了网络，则通过 [PartyNetwork::CreateInvitation](/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_createinvitation) 创建的邀请将变为无效。因此，如果一个新用户将自己添加到会话文档中，但邀请他们的用户已离开，则建议该新用户从会话文档中移除自己，并等待被其他用户重新邀请。
</Info>

## 加入正在进行的游戏

加入正在进行的游戏会话与 [平台邀请](#platform-invites) 场景非常相似。核心差异在于：不是 *玩家 A* 向 *玩家 B* 发送"邀请句柄"，而是 *玩家 B* 在从平台 UI 发起加入正在进行的游戏时，将获得一个"加入句柄"。使用此"加入句柄"，*玩家 B* 将加入会话文档并监听更改。*玩家 A* 将通过为其创建并发布一个新的 Party 邀请到会话文档来响应。*玩家 B* 将看到此新邀请以及网络描述符，并使用它加入 Party 网络。

<Info>
  如果创建邀请的 PartyLocalUser 离开了网络，则通过 [PartyNetwork::CreateInvitation](/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_createinvitation) 创建的邀请将变为无效。因此，如果一个新用户从加入正在进行的游戏流程中收到 Party 邀请，但由于创建者已离开而无法使用，则建议该新用户从会话文档中移除自己，稍后再重新加入。这将使会话中的另一个成员能够重新启动流程，并为此用户生成一个新的 Party 邀请。
</Info>

## 断开连接和清理

如果玩家离开或以其他方式与 Party 网络断开连接，他们也应该从与该 Party 网络关联的任何 MPSD 会话中移除自己。非由 [PartyNetwork::LeaveNetwork](/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_leavenetwork) 操作发起的 Party 网络断开被视为致命的。经历致命断开连接后，玩家可能会尝试重新连接并重新验证到网络中，但必须也重新加入 MPSD 会话。

如果玩家与 MPSD 会话的连接暂时中断，他们可能会与该会话断开连接。玩家可以尝试重新加入会话，但如果失败，应通过调用 [PartyNetwork::LeaveNetwork](/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_leavenetwork) 主动从 Party 网络中移除自己。

<Note>
  检测 Party 网络和 MPSD 会话断开连接的机制和启发式方法是不同的。即使在玩家将与 Party 网络和 MPSD 会话都断开连接的场景中，这些断开事件也是独立的，无法保证它们在时间上相互接近。游戏应处理玩家可能仅与 Party 网络或 MPSD 会话之一断开连接的场景。
</Note>

如果游戏关闭，玩家将自动与 Party 网络和 MPSD 文档断开连接，无需进一步清理。


## Related topics

- [使用多人 SDK 将 Party 与 Lobby 集成](/zh-CN/services/playfab/multiplayer/networking/party-lobby-integration.md)
- [将 Experiments 与其他 PlayFab 服务集成](/zh-CN/services/playfab/live-service-management/game-configuration/experiments/experiments-other-services.md)
- [将 CloudScript 操作与 PlayStream 结合使用](/zh-CN/services/playfab/data-analytics/acting-data/action-rules-using-cloudscript-actions-with-playstream.md)
- [将 Python 连接到 Insights](/zh-CN/services/playfab/data-analytics/legacy/connectivity/connecting-python-to-insights.md)
- [将 Kusto C# SDK 连接到 Insights](/zh-CN/services/playfab/data-analytics/legacy/connectivity/connecting-kusto-csharp-to-insights.md)
