> ## 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 Multiplayer 與 MPA

> 將 XBOX 遊戲從 MPSD 移轉至 PlayFab Multiplayer 大廳與多人遊戲活動 (MPA) 的移轉指南，涵蓋邀請、配對與最近一起玩的玩家。

## 簡介

本文件適用於目前使用 MPSD，並想要改用 PlayFab Multiplayer 與 MPA 來開發多人遊戲的遊戲開發人員。本文件將涵蓋最常見的多人遊戲情境，並提供示範如何搭配 MPA 使用 PlayFab Multiplayer 的程式碼片段。

### 多人遊戲工作階段目錄 (MPSD) 概觀

* 功能完整的工作階段服務，用於分享連接一群使用者所需的資訊
* 與 XBOX UI 整合，提供邀請與加入功能
* 與 SmartMatch 配對完全整合
* 工作階段衍生自預先定義的工作階段範本
* 整合了連線偵測與工作階段流程功能
* 可作為服務對服務使用

### 多人遊戲活動服務 (MPA) 概觀

* 輕量服務，可簡化玩家活動、邀請與最近一起玩的玩家的 XBOX Live 整合
* 在傳送/接受邀請以及被加入時，與殼層和主機作業系統協調
* 不提供工作階段管理或配對
* 可作為服務對服務使用

### PlayFab Multiplayer 概觀

* 完整的多人遊戲大廳服務，包含大廳搜尋與瀏覽功能
* 跨平台的即時服務通知，具有透明的 API 整合
* 支援即時通知的完整配對服務

## 初始化

下表列出 MPSD 與 PlayFab Multiplayer 用於初始化的可比較函式。

| MPSD | PlayFab Multiplayer |
| - | - |
| `XblMultiplayerAddSubscriptionLostHandler` | `PFMultiplayerInitialize` |
| `XblMultiplayerAddConnectionIdChangedHandler` | `PFMultiplayerSetEntityToken` |
| `XblMultiplayerSetSubscriptionsEnabled` | |
| `XblMultiplayerSessionCurrentUserSetStatus` | |

### 初始化 - 範例程式碼

使用您的 PlayFab titleID 初始化程式庫，並設定在登入 PlayFab 服務期間收到的實體權杖。

```cpp theme={null}
PFMultiplayerHandle pfmHandle{};
HRESULT hr = PFMultiplayerInitialize(pfTitleId, &pfmHandle);
if (FAILED(hr))
{
    //...
}

hr = PFMultiplayerSetEntityToken(pfmHandle, &entityKey, entityToken);
if (FAILED(hr))
{
    //...
}
```

## 大廳狀態變更

下表列出 MPSD 與 PlayFab Multiplayer 用於處理工作階段/大廳相關事件的可比較函式。

| MPSD | PlayFab Multiplayer |
| - | - |
| `XblMultiplayerSessionSubscribedChangeTypes` | `PFMultiplayerStartProcessingLobbyStateChanges` |
| `XblMultiplayerSessionChangedHandler` | `PFMultiplayerFinishProcessingLobbyStateChanges` |
| `XblMultiplayerSessionSubscriptionLostHandler` | |

### 大廳狀態變更 - 範例程式碼

通知程式庫您即將開始處理狀態變更。處理每個已排入佇列的狀態變更，然後通知您已完成處理狀態變更。

```cpp theme={null}
HRESULT hr = PFMultiplayerStartProcessingLobbyStateChanges(MultiplayerHandle, &StateChangeCount, &StateChanges);
if (FAILED(hr))
{
    //...
}

for (uint32_t i = 0; i < StateChangeCount; ++i)
{
    const PFLobbyStateChange& Change = *StateChanges[i];

    switch (Change.stateChangeType)
    {
    case PFLobbyStateChangeType::CreateAndJoinLobbyCompleted:        /*...*/ break;
    case PFLobbyStateChangeType::JoinLobbyCompleted:                 /*...*/ break;
    case PFLobbyStateChangeType::MemberAdded:                        /*...*/ break;
    case PFLobbyStateChangeType::AddMemberCompleted:                 /*...*/ break;
    case PFLobbyStateChangeType::MemberRemoved:                      /*...*/ break;
    case PFLobbyStateChangeType::ForceRemoveMemberCompleted:         /*...*/ break;
    case PFLobbyStateChangeType::LeaveLobbyCompleted:                /*...*/ break;
    case PFLobbyStateChangeType::Updated:                            /*...*/ break;
    case PFLobbyStateChangeType::PostUpdateCompleted:                /*...*/ break;
    case PFLobbyStateChangeType::Disconnecting:                      /*...*/ break;
    case PFLobbyStateChangeType::Disconnected:                       /*...*/ break;
    case PFLobbyStateChangeType::JoinArrangedLobbyCompleted:         /*...*/ break;
    case PFLobbyStateChangeType::FindLobbiesCompleted:               /*...*/ break;
    case PFLobbyStateChangeType::InviteReceived:                     /*...*/ break;
    case PFLobbyStateChangeType::InviteListenerStatusChanged:        /*...*/ break;
    case PFLobbyStateChangeType::SendInviteCompleted:                /*...*/ break;
    case PFLobbyStateChangeType::CreateAndClaimServerLobbyCompleted: /*...*/ break;
    case PFLobbyStateChangeType::ClaimServerLobbyCompleted:          /*...*/ break;
    case PFLobbyStateChangeType::ServerPostUpdateCompleted:          /*...*/ break;
    case PFLobbyStateChangeType::ServerDeleteLobbyCompleted:         /*...*/ break;
    }
}

hr = PFMultiplayerFinishProcessingLobbyStateChanges(MultiplayerHandle, StateChangeCount, StateChanges);
if (FAILED(hr))
{
    //...
}
```

## 建立大廳

下表列出 MPSD 與 PlayFab Multiplayer 用於建立與加入工作階段/大廳的可比較函式。

| MPSD | PlayFab Multiplayer |
| - | - |
| `XblMultiplayerSessionReferenceCreate` | `PFMultiplayerCreateAndJoinLobby` |
| `XblMultiplayerSessionCreateHandle` | |
| `XblMultiplayerSessionJoin` | |
| `XblMultiplayerAddSessionChangedHandler` | |
| `XblMultiplayerSessionSetSessionChangeSubscription` | |
| `XblMultiplayerSessionSetHostDeviceToken` | |
| `XblMultiplayerWriteSessionAsync` | |

> [!NOTE]
>
> 建立大廳不需要在 PlayFab Game Manager 中進行任何額外的設定。所有設定都可以在程式碼中完成。

### 建立大廳 - 範例程式碼

設定大廳，並設定任何初始大廳屬性或成員屬性，然後建立並加入大廳。

```cpp theme={null}
PFLobbyCreateConfiguration createConfig{};
createConfig.maxMemberCount = 4;
createConfig.ownerMigrationPolicy = PFLobbyOwnerMigrationPolicy::Automatic;
createConfig.accessPolicy = PFLobbyAccessPolicy::Public;

const char* memberPropertyKeys[] { "favoriteColor" };
const char* memberPropertyValues[] { "blue" };

PFLobbyJoinConfiguration joinConfig{};
joinConfig.memberPropertyCount = 1;
joinConfig.memberPropertyKeys = memberPropertyKeys;
joinConfig.memberPropertyValues = memberPropertyValues;

PFLobbyHandle myLobby{};

HRESULT hr = PFMultiplayerCreateAndJoinLobby(
    pfmHandle,           // PFMultiplayerHandle
    &localUserEntityKey, // local user 
    &createConfig,       // create config
    &joinConfig,         // join config
    nullptr,             // async context (optional)   
    &myLobby);           // lobby handle

if (FAILED(hr))
{
    //...
}
```

## 尋找大廳

下表列出 MPSD 與 PlayFab Multiplayer 用於搜尋工作階段/大廳的可比較函式。

| MPSD | PlayFab Multiplayer |
| - | - |
| `XblMultiplayerGetSearchHandlesAsync` | `PFMultiplayerFindLobbies` |
| `XblMultiplayerSearchHandleGetId` | |
| `XblMultiplayerSearchHandleGetCustomSessionPropertiesJson` | |
| `XblMultiplayerSearchHandleGetMemberCounts` | |
| `XblMultiplayerSearchHandleGetSessionClosed` | |

### 尋找大廳 - 範例程式碼

設定搜尋設定，然後搜尋大廳。

```cpp theme={null}
PFLobbySearchConfiguration searchConfiguration{};
searchConfiguration.filterString = filterString.c_str(); // filtering
searchConfiguration.sortString = sortString.c_str();     // sorting
searchConfiguration.clientSearchResultCount = 50;        // limits the number of results
searchConfiguration.friendsFilter;                       // return only lobbies with friends in them

HRESULT hr = PFMultiplayerFindLobbies(
    pfmHandle,          // PFMultiplayerHandle
    &localUserEntityKey,  // local user
    &searchConfiguration, // search config
    nullptr);             // async context (optional)

if (FAILED(hr))
{
    //...
} 
```

接著，傳回該事件的狀態變更後，處理任何搜尋結果。

```cpp theme={null}
const auto& stateChange = static_cast<const PFLobbyFindLobbiesCompletedStateChange&>(change);
if (SUCCEEDED(stateChange.result))
{
    for (uint32_t i = 0; i < stateChange.searchResultCount; ++i)
    {
        const PFLobbySearchResult& searchResult = stateChange.searchResults[i];
        searchResult.lobbyId;            // lobby id
        searchResult.connectionString;   // connection string
        searchResult.ownerEntity;        // lobby host
        searchResult.maxMemberCount;     // lobby size
        searchResult.currentMemberCount; // players in lobby
        
        for (uint32_t j = 0; j < searchResult.searchPropertyCount; ++j) 
        {
            const char* searchPropertyKey = searchResult.searchPropertyKeys[j];
            const char* searchPropertyValue = searchResult.searchPropertyValues[j];
            /*...*/
        }
        
        for (uint32_t k = 0; k < searchResult.friendCount; ++k) 
        {
            PFEntityKey friendEntityKey = searchResult.friends[k];
            /*...*/
        }
    }
}
else
{
    //...
}
```

## 大廳搜尋索引鍵

定義自訂搜尋屬性時，只允許使用一組受限的索引鍵。

* 字串屬性支援下列索引鍵：string\_key1、string\_key2、\[...] string\_key30
* 數值屬性支援下列索引鍵：number\_key1、number\_key2、\[...] number\_key30

## 大廳搜尋運算子

**FindLobbies** API 的查詢字串採用類似 OData 的語法結構。篩選字串的大小上限為 600 個字元。

這些 OData 運算子可用來組成查詢字串。運算子區分大小寫。

| 運算子 | 意義 | 範例 |
| - | - | - |
| eq | 等於 | string\_key1 eq 'CaptureTheFlag' |
| lt | 小於 | number\_key2 lt 10 |
| le | 小於或等於 | number\_key2 le 10 |
| gt | 大於 | number\_key3 gt 100 |
| ge | 大於或等於 | number\_key3 ge 100 |
| ne | 不等於 | string\_key1 ne 'CaptureTheFlag' |
| and | 且 | string\_key1 eq 'CaptureTheFlag' and number\_key2 lt 10 |

> [!NOTE]
>
> 比較字串屬性時，請務必以單引號括住比較值。例如，"string\_key1 eq **'SOME STRING VALUE'**"。數值屬性不需要括住。

另外也有可使用的預先定義運算子。指定時必須加上「lobby/」首碼。

| 運算子 | 意義 | 範例 |
| - | - | - |
| memberCount | 大廳中的玩家人數 | lobby/memberCount eq 5 |
| maxMemberCount | 大廳允許的最大玩家人數 | lobby/maxMemberCount gt 10 |
| memberCountRemaining | 仍可加入大廳的剩餘玩家人數 | lobby/memberCountRemaining gt 0 |
| membershipLock | 大廳的鎖定狀態，必須等於 'Unlocked' 或 'Locked' | lobby/membershipLock eq 'Unlocked' |
| amOwner | 您是擁有者的大廳，必須等於 'true' | lobby/amOwner eq 'true' |
| amMember | 您是成員的大廳，必須等於 'true' | lobby/amMember eq 'true' |
| amServer | 伺服器已加入的用戶端擁有大廳，必須等於 'true' | lobby/amServer eq 'true' |

## 排序搜尋結果

OData 樣式的字串，包含此查詢的遞增 ("asc") 或遞減 ("desc") 排序。OrderBy 子句可用於任何搜尋數字索引鍵，或數值型的預先定義搜尋索引鍵。若要依最接近某個數字排序，可以使用 distance 名稱，依與指定數字搜尋索引鍵的距離排序。distance 排序不能搭配遞增或遞減使用。此欄位只支援一個排序子句或一個 distance 子句。如果未提供排序，或指定的排序需要決勝條件，預設排序會依建立時間遞減排序。

| 範例 | 意義 |
| - | - |
| number\_key1 asc | 依數字搜尋索引鍵遞增排序 |
| lobby/memberCount desc | 依數字搜尋索引鍵遞減排序 |
| distance(number\_key1 = 5) | 依與指定數字的距離排序 |
| | 依建立時間遞減排序 |

### 排序與篩選搜尋結果 - 範例程式碼

```cpp theme={null}
PFLobbySearchConfiguration searchConfiguration{};​
​
PFLobbySearchFriendsFilter friendsFilter{};    ​
friendsFilter.includeXboxFriendsToken = MyGame::GetLocalUserXboxToken();​
searchConfiguration.friendsFilter = &friendsFilter;​

// Create filter string for ranked deathmatch with skill between 10-20​
std::string filterString;​
filterString +=  "string_key1 eq DeathMatch and ";​ 
filterString +=  "string_key2 eq Ranked and ";​
filterString +=  "number_key1 -ge 10 and ";​
filterString +=  "number_key1 -le 20";​
​
// Create sort string based on skill level​
std::string sortString;​
sortString += std::string("distance{number_key1=" + std::to_string(playerSkill.c_str()) + "}";​

searchConfiguration.filterString = filterString.c_str();​
searchConfiguration.sortString = sortString.c_str();​
searchConfiguration.clientSearchResultCount = 10;        // limits the number of results​
```

## 加入大廳

下表列出 MPSD 與 PlayFab Multiplayer 用於加入工作階段/大廳的可比較函式。

| MPSD | PlayFab Multiplayer |
| - | - |
| `XblMultiplayerGetSessionByHandleAsync` | `PFMultiplayerJoinLobby` |
| `XblMultiplayerSessionJoin` | |
| `XblMultiplayerAddSessionChangedHandler` | |
| `XblMultiplayerSessionSetSessionChangeSubscription` | |
| `XblMultiplayerSessionCurrentUserSetStatus` | |
| `XblMultiplayerWriteSessionByHandleAsync` | |
| `XblMultiplayerSessionCloseHandle` | |

> [!NOTE]
>
> 加入大廳需要連接字串。通常，大廳的主機會在其活動上設定此連接字串，或透過邀請傳送。若要取得連接字串，您必須呼叫 `PFLobbyGetConnectionString`。

### 加入大廳 - 範例程式碼

設定初始加入設定，然後加入大廳。

```cpp theme={null}
const char* memberPropertyKeys[] { "number", "name"};
const char* memberPropertyValues[] { "8675309", "Jenny"};

PFLobbyJoinConfiguration joinConfig{};
joinConfig.memberPropertyCount = 2;
joinConfig.memberPropertyKeys = memberPropertyKeys;
joinConfig.memberPropertyValues = memberPropertyValues;

PFLobbyHandle myLobby{};

HRESULT hr = PFMultiplayerJoinLobby(
    pfmHandle,             // PFMultiplayerHandle
    &localUserEntityKey,   // local user
    lobbyConnectionString, // connection string
    &joinConfig,           // join config
    nullptr,               // async context (optional)
    &myLobby);             // handle to the lobby

if (FAILED(hr))
{
    //...
} 
```

## 更新大廳

下表列出 MPSD 與 PlayFab Multiplayer 用於更新工作階段/大廳的可比較函式。

| MPSD | PlayFab Multiplayer |
| - | - |
| `XblMultiplayerGetSessionByHandleAsync` | `PFLobbyPostUpdate` |
| `XblMultiplayerWriteSessionByHandleAsync` | |
| `XblMultiplayerSessionCloseHandle` | |

> [!NOTE]
>
> `PFLobbyPostUpdate` 可用來同時更新大廳屬性與成員屬性。您可以透過單次呼叫該函式，更新其中一種或兩種類型的屬性。

### 更新大廳 - 範例程式碼 (大廳屬性)

```cpp theme={null}
const char* lobbyPropertyKeys[] { "exampleKey_1", "exampleKey_2" };
const char* lobbyPropertyValues[] { "exampleValue_1234", "exampleValue_ABCD" };

PFLobbyDataUpdate lobbyUpdateData{};
lobbyUpdateData.lobbyPropertyCount = 2;
lobbyUpdateData.lobbyPropertyKeys = lobbyPropertyKeys;
lobbyUpdateData.lobbyPropertyValues = lobbyPropertyValues;

HRESULT hr = PFLobbyPostUpdate(
    myLobby,             // handle to the lobby
    &localUserEntityKey, // local user
    &lobbyUpdateData,    // update data for the lobby
    nullptr,             // update data for a member
    nullptr);            // async context (optional)

if (FAILED(hr))
{
    //...
} 
```

### 更新大廳 - 範例程式碼 (成員屬性)

```cpp theme={null}
const char* memberPropertyKeys[] { "favoriteColor" };
const char* memberPropertyValues[] { "yellow" };

PFLobbyMemberDataUpdate memberUpdateData{};
memberUpdateData.lobbyPropertyCount = 1;
memberUpdateData.lobbyPropertyKeys = memberPropertyKeys;
memberUpdateData.lobbyPropertyValues = memberPropertyKeys;

HRESULT hr = PFLobbyPostUpdate(
    myLobby,             // handle to the lobby
    &localUserEntityKey, // local user
    nullptr,             // update data for the lobby
    & memberUpdateData   // update data for a member
    nullptr);            // async context (optional)

if (FAILED(hr))
{
    //...
}
```

## 配對

PlayFab Multiplayer 的配對 API 與 MPSD 的配對 API 相當類似。

| MPSD | PlayFab Multiplayer |
| - | - |
| 透過 Smartmatch Hopper 設定 | 以配對佇列為基礎 |
| Hopper 繫結至 MPSD 工作階段範本 | 配對規則套用於佇列 |
| 配對規則套用於 Hopper | 配對透過對戰票證開始 |
| 配對需要現有的 MPSD 工作階段 | 配對結果是新的 PlayFab 大廳 |
| 配對結果是新的 MPSD 工作階段 | 支援 PlayFab Multiplayer 伺服器配置 |

> [!NOTE]
>
> 配對佇列必須透過 PlayFab Game Manager 設定。

## 配對狀態變更

下表列出 MPSD 與 PlayFab Multiplayer 用於處理配對相關事件的可比較函式。

| MPSD | PlayFab Multiplayer |
| - | - |
| `XblMultiplayerSessionSubscribedChangeTypes` | `PFMultiplayerStartProcessingMatchmakingStateChanges` |
| `XblMultiplayerSessionChangedHandler` | `PFMultiplayerFinishProcessingMatchmakingStateChanges` |
| `XblMultiplayerSessionSubscriptionLostHandler` | |

### 配對狀態變更 - 範例程式碼

通知程式庫您即將開始處理狀態變更。處理每個已排入佇列的狀態變更，然後通知您已完成處理狀態變更。

```cpp theme={null}
uint32_t stateChangeCount = 0;
const PFMatchmakingStateChange* const* stateChanges = nullptr;

HRESULT hr = PFMultiplayerStartProcessingMatchmakingStateChanges(pfmHandle, &stateChangeCount, &stateChanges);
if (FAILED(hr))
{
    //...
}

for (uint32 i = 0; i < stateChangeCount; ++i)
{
    const PFMatchmakingStateChange& change = *stateChanges[i];
    
    switch (change.stateChangeType)
    {
    case PFMatchmakingStateChangeType::TicketStatusChanged: /*...*/ break;
    case PFMatchmakingStateChangeType::TicketCompleted:     /*...*/ break;
    }
}

hr = PFMultiplayerFinishProcessingMatchmakingStateChanges(pfmHandle, stateChangeCount, stateChanges);
if (FAILED(hr))
{
    //...
}
```

## 開始配對

| MPSD | PlayFab Multiplayer |
| - | - |
| 建立 MPSD 工作階段的呼叫... | `PFMultiplayerCreateMatchmakingTicket` |
| `XblMatchmakingCreateMatchTicketAsync` | `PFMultiplayerJoinMatchmakingTicketFromId` |
| `XblMatchmakingCreateMatchTicketResult` | `PFMatchmakingTicketGetStatus` |
| `XblMultiplayerSessionMatchmakingServer` | `PFMatchmakingTicketGetMatch` |
| 加入 MPSD 工作階段的呼叫... | `PFMultiplayerJoinArrangedLobby` |

### 開始配對 - 範例程式碼

```cpp theme={null}
PFMatchmakingTicketConfiguration matchTicketConfig{};
matchTicketConfig.timeoutInSeconds;        // how long to attempt matchmaking
matchTicketConfig.queueName;               // matchmaking queue name
matchTicketConfig.membersToMatchWithCount; // num remote players to go into matchmaking with
matchTicketConfig.membersToMatchWith;      // remote players to go into matchmaking with

HRESULT hr = PFMultiplayerCreateMatchmakingTicket(
    pfmHandle,                   // PFMultiplayerHandle
    1,                           // local user count
    &currentUserEntityKey,       // local users
    nullptr,                     // local user attributes (optional)
    &ticketConfig,               // ticket config
    nullptr,                     // async context (optional)
    &m_activeMatchmakingTicket); // matchmaking ticket

if (FAILED(hr))
{
    //...
} 

hr = PFMultiplayerJoinMatchmakingTicketFromId(
    pfmHandle,                   // PFMultiplayerHandle
    1,                           // local user count
    &currentUserEntityKey,       // local users
    nullptr,                     // local user attributes (optional)
    ticketId,                    // matchmaking ticket to join
    queueName,                   // matchmaking queue name
    nullptr,                     // async context (optional)
    &m_activeMatchmakingTicket); // matchmaking ticket

if (FAILED(hr))
{
    //...
}
```

> [!NOTE]
>
> 在 `membersToMatchWith` 欄位中指定的所有成員都加入之前，配對不會開始。

接著，找到對戰並傳回狀態變更後，加入已安排的大廳。

```cpp theme={null}
const auto& stateChange = static_cast<const PFMatchmakingTicketCompletedStateChange&>(change);
if (SUCCEEDED(stateChange.result))
{
    PFMatchmakingTicketStatus status{};
    HRESULT hr = PFMatchmakingTicketGetStatus(stateChange.ticket, &status);
    if (SUCCEEDED(hr))
    {
        if (status == PFMatchmakingTicketStatus::Matched)
        {
            const PFMatchmakingMatchDetails* matchDetails = nullptr;
            hr = PFMatchmakingTicketGetMatch(stateChange.ticket, &matchDetails);
            if (SUCCEEDED(hr))
            {
                const char* memberPropertyKeys[] { "favoriteCheese" };
                const char* memberPropertyValues[] { "Wensleydale" };

                PFLobbyArrangedJoinConfiguration joinConfig{};
                joinConfig.accessPolicy = PFLobbyAccessPolicy::Private;
                joinConfig.maxMemberCount = 4;
                joinConfig.ownerMigrationPolicy = PFLobbyOwnerMigrationPolicy::Automatic;
                joinConfig.memberPropertyCount = 1;
                joinConfig.memberPropertyKeys = memberPropertyKeys;
                joinConfig.memberPropertyValues = memberPropertyValues;
                
                PFLobbyHandle myLobby{};

                hr = PFMultiplayerJoinArrangedLobby(
                    pfmHandle,                            // PFMultiplayerHandle
                    &localUserEntityKey,                  // local user
                    matchDetails->lobbyArrangementString, // connection string
                    &config,                              // join config
                    nullptr,                              // async context (optional)
                    &myLobby);                            // handle to the lobby

                if (FAILED(hr))
                {
                    //...
                }
            }
        }
    }
}
```

## 清除

下表列出 MPSD 與 PlayFab Multiplayer 用於清除與關閉的可比較函式。

| MPSD | MPA |
| - | - |
| `XblMultiplayerRemoveSubscriptionLostHandler` | `PFMultiplayerUninitialize` |
| `XblMultiplayerRemoveConnectionIdChangedHandler` | |
| `XblMultiplayerSetSubscriptionsEnabled` | |

> [!NOTE]
>
> 呼叫 `PFMultiplayerUninitialize` 之前，請務必離開所有作用中的大廳，並終結所有進行中的配對票證。

### 清除 - 範例程式碼

```cpp theme={null}
HRESULT hr = PFMultiplayerUninitialize(pfmHandle);
if (FAILED(hr))
{
    //...
}
```

## 活動

下表列出 MPSD 與 MPA 用於管理活動的可比較函式。

| MPSD | MPA |
| - | - |
| `XblMultiplayerSetActivityAsync` | `XblMultiplayerActivitySetActivityAsync` |
| `XblMultiplayerClearActivityAsync` | `XblMultiplayerActivityDeleteActivityAsync` |
| | `XblMultiplayerActivityGetActivityAsync` |
| | `XblMultiplayerActivityGetActivityResultSize` |
| | `XblMultiplayerActivityGetActivityResult` |

> [!NOTE]
>
> 設定活動或傳送邀請時，請務必使用從 `PFLobbyGetConnectionString` 傳回的連接字串。

### 活動 - 範例程式碼

```cpp theme={null}
const char* connectionString;
HRESULT hr = PFLobbyGetConnectionString(myLobby, &connectionString);
if (FAILED(hr))
{
    //...
}

uint32_t maxPlayerCount;
hr = PFLobbyGetMaxMemberCount(myLobby, &maxPlayerCount);
if (FAILED(hr))
{
    //...
}

uint32_t lobbyMemberCount;
const PFEntityKey* lobbyMembers;
hr = PFLobbyGetMembers(myLobby, &lobbyMemberCount, &lobbyMembers);
if (FAILED(hr))
{
    //...
}

const char* lobbyId;
hr = PFLobbyGetLobbyId(myLobby, &lobbyId);
if (FAILED(hr))
{
    //...
}

PFLobbyAccessPolicy pfAccessPolicy;
hr = PFLobbyGetAccessPolicy(LobbyHandle, &pfAccessPolicy);
if (FAILED(hr))
{
    //...
}

XblMultiplayerActivityJoinRestriction joinRestriction = XblMultiplayerActivityJoinRestriction::InviteOnly;

switch (pfAccessPolicy)
{
case PFLobbyAccessPolicy::Public:  joinRestriction = XblMultiplayerActivityJoinRestriction::Public; break;
case PFLobbyAccessPolicy::Friends: joinRestriction = XblMultiplayerActivityJoinRestriction::Followed; break;
case PFLobbyAccessPolicy::Private: joinRestriction = XblMultiplayerActivityJoinRestriction::InviteOnly; break;
}

XblMultiplayerActivityInfo info{};
info.connectionString = connectionString;
info.joinRestriction = joinRestriction;
info.maxPlayers = maxPlayerCount;
info.currentPlayers = lobbyMemberCount;
info.groupId = lobbyId;
info.xuid = myXuid;

auto async = std::make_unique<XAsyncBlock>();
async->queue = queue;
async->callback = [](XAsyncBlock* async) 
{
    std::unique_ptr<XAsyncBlock> asyncBlockPtr{ async };
    HRESULT hr = XAsyncGetStatus(async, false);
    if(FAILED(hr))
    {
        //...
    }
};

HRESULT hr = XblMultiplayerActivitySetActivityAsync(
    xblContext,    // XblContextHandle
    &info,         // XblMultiplayerActivityInfo
    false,         // Allow cross-platform joins
    async.get()    // XAsyncBlock
);

if (SUCCEEDED(hr)) 
{
    async.release();
}
else
{
    //...
}
```

## 邀請

下表列出 MPSD 與 MPA 用於傳送與接收邀請的可比較函式。

| MPSD | MPA |
| - | - |
| `XGameInviteRegisterForEvent` | `XGameInviteRegisterForEvent` |
| `XGameInviteUnregisterForEvent` | `XGameInviteUnregisterForEvent` |
| `XblMultiplayerSendInvitesAsync` | `XblMultiplayerActivitySendInvitesAsync` |
| `XGameUiShowSendGameInviteAsync` | `XGameUiShowMultiplayerActivityGameInviteAsync` |

### 邀請 - 範例程式碼 (遊戲 UI)

```cpp theme={null}
const char* connectionString;
HRESULT hr = PFLobbyGetConnectionString(myLobby, &connectionString);
if (SUCCEEDED(hr))
{
    auto async = std::make_unique<XAsyncBlock>();
    async->queue = queue;
    async->callback = [](XAsyncBlock* async)
    {
        std::unique_ptr<XAsyncBlock> asyncBlockPtr{ async };
        HRESULT hr = XAsyncGetStatus(async, false);
        if(FAILED(hr))
        {
            //...   
        }
    };
    
    HRESULT hr = XblMultiplayerActivitySendInvitesAsync(
        xblContext,       // XblContextHandle 
        &xuid,            // recipient
        1,                // number of invited XUIDs
        true,             // allow cross-platform joins
        connectionString, // use lobby connection string
        async.get());

    if (SUCCEEDED(hr))
    {
        async.release();
    }
    else
    {
        //...
    }
}
else
{
    //...
}
```

### 邀請 - 範例程式碼 (XBOX UI)

```cpp theme={null}
auto async = std::make_unique<XAsyncBlock>();
async->queue = queue;
async->callback = [](XAsyncBlock* async)
{
    std::unique_ptr<XAsyncBlock> async{ async };
    HRESULT hr = XGameUiShowMultiplayerActivityGameInviteResult(async);
    if(FAILED(hr))
    {
        //...   
    }
};

HRESULT hr = XGameUiShowMultiplayerActivityGameInviteAsync(
    async.get(), // XAsyncBlock
    user.get()   // XUserHandle that is sending the invite
);

if (SUCCEEDED(hr))
{
    async.release();
}
else
{
    //...
}
```

> [!NOTE]
>
> `XGameUiShowMultiplayerActivityGameInviteResult` 會使用目前設定的活動。使用此函式之前，您必須先使用 `XblMultiplayerActivitySetActivityAsync` 設定活動。

## 最近一起玩的玩家

下表顯示使用 MPSD 與 MPA 時，如何管理最近一起玩的玩家清單。

| MPSD | MPA |
| - | - |
| 玩家必須位於同一個 MPSD 工作階段中 | `XblMultiplayerActivityUpdateRecentPlayers` |
| 工作階段的 `gameplay` 屬性設為 `true` | `XblMultiplayerActivityFlushRecentPlayersAsync` |
| 兩位玩家都標記為作用中 | |

> [!NOTE]
>
> 為了避免節流，最佳做法是批次處理對 `XblMultiplayerActivityUpdateRecentPlayers` 的呼叫。

### RecentPlayers - 範例程式碼

```cpp theme={null}
XblMultiplayerActivityRecentPlayerUpdate update{};
update.xuid = metPlayerXuid;
update.encounterType = XblMultiplayerActivityEncounterType::Default;

HRESULT hr = XblMultiplayerActivityUpdateRecentPlayers(xblContext, &update, 1);
if (FAILED(hr))
{
    //...
}
```


## Related topics

- [與 PlayFab Multiplayer Servers 整合](/zh-TW/services/playfab/multiplayer/matchmaking/multiplayer-servers.md)
- [適用於遊戲創作者的 PlayFab 與 Microsoft Fabric 即時分析 (RTA)](/zh-TW/services/playfab/data-analytics/learn-data/reports/real-time-analytics-tutorial.md)
- [XBOX 與 PC 多人遊戲設計指引](/zh-TW/services/xbox-services/multiplayer/overviews/multiplayer-design-guidance-xbox-pc-gdk.md)
- [概念](/zh-TW/services/xbox-services/multiplayer/mpsd/concepts/index.md)
- [MPA](/zh-TW/services/xbox-services/multiplayer/mpa/index.md)
