> ## 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.

# 搭配 MPSD 使用 PlayFab Party

> 將 PlayFab Party 語音和資料網路功能與 XBOX 多人遊戲工作階段目錄 (MPSD) 結合，以進行跨網路的工作階段管理和邀請。

XBOX 多人遊戲案例依賴多人遊戲工作階段目錄 (MPSD) 服務和 MPSD 文件。MPSD 文件可作為您目前遊戲工作階段的名冊，並驅動配對、平台邀請、最近玩家清單和中途加入等多人遊戲體驗。

本文件將說明如何將 PlayFab Party 納入需要 MPSD 的常見多人遊戲流程中。

本文件不會深入討論 MPSD 及其所有功能。如需詳細資訊，請參閱 [MPSD 文件](/zh-TW/services/xbox-services/multiplayer/mpsd/live-mpsd-overview)。

## 配對

以下是搭配 PlayFab Party 使用配對和 MPSD 的簡化流程：

1. 玩家會建立並聚集到 MPSD 工作階段中，這些工作階段代表他們想要在各個配對工作階段中一起遊玩的群組。玩家會使用 XBOX 的邀請和加入功能聚集到這些工作階段中。

2. 這些玩家群組會向配對服務提交票證，配對服務會將相容的玩家群組聚集到配對工作階段中。此配對工作階段本身會以新的工作階段文件表示，玩家接著會加入該文件。玩家也必須接聽此工作階段文件的變更。

3. 配對工作階段完成且名冊鎖定後，遊戲必須從配對工作階段的成員中選出一位來設定 PlayFab Party 網路。選取 Party 網路建立者的簡單策略，是使用配對 MPSD 工作階段文件的第一位成員。

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>

<a id="platform-invites" />

## 平台邀請

以下是將 XBOX 平台邀請納入 PlayFab Party 的流程：

1. *PlayerA* 建立 MPSD 工作階段文件、接聽工作階段變更，並建立 Party 網路。Party 網路建立完成後，*PlayerA* 會將網路描述元和初始邀請 (如有必要) 發佈至 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. 當 *PlayerA* 想要邀請 *PlayerB* 加入 Party 網路時，*PlayerA* 會透過遊戲內或主機 UI 向 *PlayerB* 發起平台邀請。

3. *PlayerB* 收到平台邀請，其中包含「邀請控制代碼」，*PlayerB* 可以用它來尋找 *PlayerA* 的 MPSD 工作階段文件。

4. *PlayerB* 加入工作階段文件並接聽變更。

5. *PlayerA* 看到 *PlayerB* 加入工作階段文件。*PlayerA* 建立新的邀請供 *PlayerB* 使用，並將該邀請發佈至工作階段文件

   ```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. *PlayerB* 看到發佈至工作階段文件的邀請，並使用它加入 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](/zh-TW/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_createinvitation) 建立的邀請就會失效。因此，如果新使用者將自己新增至工作階段文件，但邀請他們的使用者已離開，建議新使用者將自己從工作階段文件中移除，並等待其他使用者重新邀請。
</Info>

## 中途加入

加入進行中的遊戲工作階段與[平台邀請](#platform-invites)案例非常類似。核心差異在於，*PlayerB* 不是從 *PlayerA* 收到「邀請控制代碼」，而是在從平台 UI 發起中途加入時取得「加入控制代碼」。*PlayerB* 會使用此「加入控制代碼」加入工作階段文件並接聽變更。*PlayerA* 會透過建立新的 Party 邀請並將其發佈至工作階段文件來回應。*PlayerB* 會看到這個新邀請以及網路描述元，並使用它加入 Party 網路。

<Info>
  如果建立邀請的 PartyLocalUser 離開網路，透過 [PartyNetwork::CreateInvitation](/zh-TW/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_createinvitation) 建立的邀請就會失效。因此，如果新使用者從中途加入流程收到 Party 邀請，但因為建立該邀請的使用者已離開而無法使用，建議新使用者將自己從工作階段文件中移除，並於稍後重新加入。這可讓工作階段的其他成員重新啟動流程，並為此使用者產生新的 Party 邀請。
</Info>

## 中斷連線和清除

如果玩家離開 Party 網路或因其他原因與其中斷連線，也應將自己從與該 Party 網路相關聯的任何 MPSD 工作階段中移除。非由 [PartyNetwork::LeaveNetwork](/zh-TW/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_leavenetwork) 作業起始的 Party 網路中斷連線會被視為嚴重錯誤。發生嚴重中斷連線後，玩家可以嘗試重新連線並重新驗證進入網路，但也必須重新加入 MPSD 工作階段。

如果玩家與 MPSD 工作階段的連線暫時中斷，他們可能會與該工作階段中斷連線。玩家可以嘗試重新加入工作階段，但如果失敗，應呼叫 [PartyNetwork::LeaveNetwork](/zh-TW/services/playfab/multiplayer/networking/reference/classes/PartyNetwork/methods/partynetwork_leavenetwork) 自願將自己從 Party 網路中移除。

<Note>
  偵測 Party 網路和 MPSD 工作階段中斷連線的機制和啟發學習法並不相同。即使在玩家會同時與 Party 網路和 MPSD 工作階段中斷連線的案例中，這些中斷連線事件也是獨立的，不保證它們會在時間上相近地發生。遊戲應處理玩家可能只與 Party 網路或 MPSD 工作階段其中之一中斷連線的案例。
</Note>

如果遊戲關閉，玩家會自動與 Party 網路和 MPSD 文件中斷連線，不需要進一步清除。


## Related topics

- [XBOX 需求](/zh-TW/services/playfab/multiplayer/networking/xbox-requirements.md)
- [搭配 Unity 使用 GDK](/zh-TW/build/gdk-and-engines/unity/unity.md)
- [從 MPSD 移至 PlayFab Multiplayer 與 MPA](/zh-TW/services/xbox-services/multiplayer/mpsd/concepts/live-mpsd-to-mlp.md)
- [使用 PlayFab 加入後端服務](/zh-TW/home/build-first-title/add-backend-services.md)
- [搭配 Unreal Engine 使用 GDK](/zh-TW/build/gdk-and-engines/unreal/unreal.md)
