> ## 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 用于其多人游戏的游戏开发者。本文档将涵盖最常见的多人游戏场景,并提供演示如何将 PlayFab Multiplayer 与 MPA 一起使用的代码片段。

### 多人游戏会话目录 (MPSD) 概述

* 用于共享连接一组用户所需信息的功能齐全的会话服务
* 与 XBOX UI 集成,支持邀请和加入功能
* 与 SmartMatch 匹配完全集成
* 会话派生自预定义的会话模板
* 用于连接检测和会话流的集成功能
* 可作为服务到服务提供

### 多人游戏活动服务 (MPA) 概述

* 轻量级服务,用于简化玩家活动、邀请和最近玩家的 XBOX Live 集成
* 与 shell 和主机操作系统协作,发送/接受邀请和被加入
* 无会话管理或匹配
* 可作为服务到服务提供

### 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 中创建大厅无需其他设置或配置。所有配置都可以在代码中完成。</Note>

### 创建大厅 - 示例代码

配置大厅并设置任何初始大厅属性或成员属性,然后创建并加入大厅。

```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'**"。数字属性无需包裹。</Note>

还有可供使用的预定义运算符。指定时它们必须以“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 子句可用于任何搜索数字键或数字型预定义搜索键。若要按最接近某个数字排序,可以使用距离标识符按与给定数字搜索键的距离排序。您不能对距离排序使用升序或降序。此字段仅支持一个排序子句或一个距离子句。如果未提供排序,或者给定排序需要平局决胜,则默认排序将基于创建时间降序。

| 示例                         | 含义          |
| -------------------------- | ----------- |
| 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`。</Note>

### 加入大厅 - 示例代码

设置初始加入配置,然后加入大厅。

```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` 可用于更新大厅属性以及成员属性。您可以通过一次函数调用来更新一种或两种类型的属性。</Note>

### 更新大厅 - 示例代码(大厅属性)

```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 Hoppers 配置 | 基于匹配队列                       |
| Hopper 绑定到 MPSD 会话模板     | 匹配规则应用于队列                    |
| 匹配规则应用于 Hopper           | 匹配通过匹配票证开始                   |
| 匹配需要现有的 MPSD 会话          | 匹配结果是新的 PlayFab 大厅           |
| 匹配结果是新的 MPSD 会话          | 支持 PlayFab Multiplayer 服务器分配 |

<Note>匹配队列必须通过 PlayFab Game Manager 配置。</Note>

## 匹配状态更改

下表显示了 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` 字段中指定的所有成员都加入后才会开始。</Note>

然后,一旦找到匹配并返回状态更改,加入已安排的大厅。

```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` 之前,请确保离开所有活动大厅并销毁所有进行中的匹配票证。</Note>

### 清理 - 示例代码

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

## 活动

下表显示了 MPSD 和 MPA 用于管理活动的可比函数列表。

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

<Note>设置活动或发送邀请时,请确保使用从 `PFLobbyGetConnectionString` 传回的连接字符串。</Note>

### 活动 - 示例代码

```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` 设置活动。</Note>

## 最近玩家

下表显示了使用 MPSD 和 MPA 时如何管理最近玩家列表。

| MPSD                        | MPA                                             |
| --------------------------- | ----------------------------------------------- |
| 玩家必须在同一个 MPSD 会话中           | `XblMultiplayerActivityUpdateRecentPlayers`     |
| 会话将 `gameplay` 属性设置为 `true` | `XblMultiplayerActivityFlushRecentPlayersAsync` |
| 两位玩家都被标记为活动                 |                                                 |

<Note>为避免限流,最佳做法是批量调用 `XblMultiplayerActivityUpdateRecentPlayers`。</Note>

### 最近玩家 - 示例代码

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

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


## Related topics

- [从 PlayFab 独立 SDK v1 迁移到统一 SDK v2](/zh-CN/services/playfab/sdks/unified-sdk/migrating-from-v1.md)
- [概念](/zh-CN/services/xbox-services/multiplayer/mpsd/concepts/index.md)
- [与 PlayFab Lobby 和 Match 集成](/zh-CN/services/xbox-services/multiplayer/mpa/concepts/live-mpa-playfab-integration.md)
- [Entities 迁移信息](/zh-CN/services/playfab/live-service-management/game-configuration/entities/migration-information.md)
- [在 Unity 中从 Google Sign-in 迁移到 Google Play Games](/zh-CN/services/playfab/identity/player-identity/platform-specific-authentication/google-play-games-sign-in-migration-details.md)
