> ## 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 로비 및 Multiplayer Activity(MPA)로 이전하기 위한 마이그레이션 가이드입니다.

## 소개

이 문서는 현재 MPSD를 사용하고 있으며 멀티플레이어 게임에 PlayFab Multiplayer 및 MPA를 사용하도록 이동하려는 게임 개발자를 대상으로 합니다. 이 문서는 가장 일반적인 멀티플레이어 시나리오를 다루고 PlayFab Multiplayer를 MPA와 함께 사용하는 방법을 보여주는 코드 스니펫을 제공합니다.

### Multiplayer Session Directory(MPSD) 개요

* 사용자 그룹을 연결하는 데 필요한 정보를 공유하기 위한 모든 기능을 갖춘 세션 서비스
* 초대 및 참가 기능을 위한 XBOX UI와 통합
* SmartMatch 매치메이킹과 완전히 통합
* 세션은 사전 정의된 세션 템플릿에서 파생됨
* 연결 감지 및 세션 흐름을 위한 통합 기능
* 서비스 간 사용 가능

### Multiplayer Activity Service(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에서 추가 설정이나 구성이 필요하지 않습니다. 모든 구성은 코드에서 수행할 수 있습니다.</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'           |

## 검색 결과 정렬

이 쿼리에 대한 정렬을 오름차순("asc") 또는 내림차순("desc")으로 포함하는 OData 스타일 문자열입니다. 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 호퍼를 통한 구성   | 매치메이킹 큐 기반                   |
| 호퍼는 MPSD 세션 템플릿에 바인딩됨  | 매치메이킹 규칙이 큐에 적용됨             |
| 매치메이킹 규칙이 호퍼에 적용됨      | 매치메이킹이 매치 티켓을 통해 시작됨         |
| 매치메이킹에 기존 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>

그런 다음, 매치가 발견되고 상태 변경이 반환되면 arranged lobby에 참가합니다.

```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

- [개념](/ko/services/xbox-services/multiplayer/mpsd/concepts/index.md)
- [MPA 초대 및 활동 문제 해결 가이드](/ko/services/xbox-services/multiplayer/mpa/concepts/live-mpa-troubleshooting.md)
- [게임 서버 로컬 디버깅 및 PlayFab과의 통합](/ko/services/playfab/multiplayer/servers/locally-debugging-game-servers-and-integration-with-playfab.md)
- [멀티플레이어 FAQ 및 문제 해결](/ko/services/xbox-services/multiplayer/mpsd/concepts/live-multiplayer-2015-faq.md)
- [MPA](/ko/services/xbox-services/multiplayer/mpa/index.md)
