다음을 통해 공유


MPSD에서 PlayFab 파티 사용

Xbox 멀티 플레이어 시나리오에서는 MPSD(멀티 플레이어 세션 디렉터리) 서비스 및 MPSD 문서를 사용합니다. MPSD 문서는 현재 게임 세션의 명단 역할을 하며 매치 메이킹, 플랫폼 초대, 최근 플레이어 목록, 진행 중 참가와 같은 멀티 플레이어 환경을 구동합니다.

이 문서에서는 MPSD가 필요한 일반 멀티 플레이어 흐름에 PlayFab 파티를 통합하는 방법을 설명합니다.

MPSD와 모든 기능에 대한 자세한 내용은 이 문서에서 다루지 않습니다. 자세한 내용은 MPSD 설명서를 참조하세요.

매치 메이킹

PlayFab 파티와 함께 매치 메이킹 및 MPSD를 사용하는 간략한 방법은 아래와 같습니다.

  1. 플레이어는 매치 메이킹 세션에서 함께 플레이할 그룹을 나타내는 MPSD 세션을 만들고 해당 세션으로 모입니다. 플레이어는 Xbox의 초대 및 참가 기능을 사용하여 세션으로 모입니다.

  2. 플레이어 그룹은 매치 메이킹 서비스에 티켓을 제출하여 호환되는 플레이어 그룹을 매치 메이킹 세션으로 모읍니다. 매치 메이킹 세션 자체는 플레이어가 참가할 새 세션 문서로 표시됩니다. 또한 플레이어는 이 세션 문서의 변경 내용을 수신 대기해야 합니다.

  3. 매치 메이킹 세션이 종료되고 명단이 잠기면 타이틀은 매치 메이킹 세션의 멤버 중 하나를 선택하여 PlayFab 파티 네트워크를 설정해야 합니다. 파티 네트워크 작성자를 선택하는 간단한 전략은 매치 메이킹 MPSD 세션 문서의 첫 번째 멤버를 사용하는 것입니다.

  4. 선택된 멤버는 매치 메이킹 세션의 멤버로 네트워크 액세스를 제한하는 초기 PartyInvitation을(를) 사용하여 네트워크를 만듭니다. 네트워크 만들기가 완료되면 선택된 멤버는 결과 네트워크 설명자 및 파티 초대를 세션 문서에 다른 멤버가 사용할 세션 속성으로 게시해야 합니다.

    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. 각 멤버는 업데이트된 세션 문서를 확인한 후 네트워크 설명자와 초대를 사용하여 네트워크에 연결하고 참가할 수 있습니다.

    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;
        }
    }
    

참고 항목

여기서는 매치 메이킹 및 MPSD를 PlayFab 파티와 통합하는 한 가지 흐름을 살펴보았습니다. 이 흐름의 핵심 아이디어를 MPSD에서 관심 있는 다른 흐름으로 확장할 수 있지만, 모든 가능한 흐름을 살펴보는 것은 이 설명서의 범위를 벗어났습니다. 자세한 내용은 전체 MPSD 설명서을 참조하세요.

플랫폼 초대

Xbox 플랫폼 초대를 PlayFab 파티에 통합하는 방법은 다음과 같습니다.

  1. PlayerA는 MPSD 세션 문서를 만들고, 세션 변경을 수신 대기하고, 파티 네트워크를 만듭니다. 파티 네트워크 만들기가 완료되면 *PlayerA는 *네트워크 설명자와 초기 초대(필요한 경우)를 MPSD 세션 문서에 게시합니다.

    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. PlayerAPlayerB를 파티 네트워크에 초대하려는 경우 인게임 또는 콘솔 UI를 통해 PlayerB 대한 플랫폼 초대를 시작합니다.

  3. PlayerBPlayerA의 MPSD 세션 문서를 찾는 데 사용할 수 "초대 핸들"이 포함된 플랫폼 초대를 받습니다.

  4. PlayerB는 세션 문서에 조인하고 변경 내용을 수신 기다립니다.

  5. PlayerAPlayerB가 세션 문서에 조인한 것을 확인합니다. PlayerAPlayerB가 사용할 새 초대를 만들고 해당 초대를 세션 문서에 게시합니다.

    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는 세션 문서에 게시된 초대를 확인하고 초대를 사용하여 파티 네트워크에 참가합니다.

    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;
        }
    }
    

    Important

    PartyNetwork::CreateInvitation을 통해 만든 초대는 해당 초대를 만든 PartyLocalUser가 네트워크에서 나가면 무효화됩니다. 따라서 새 사용자는 세션 문서에 본인을 추가한 후 자신을 초대한 사용자가 나갈 경우 세션 문서에서 자신을 제거하고 다른 사용자가 다시 초대할 때까지 기다리는 것이 좋습니다.

진행 중 참가

진행 중인 게임 세션 참가하는 것은 플랫폼 초대 시나리오와 매우 유사합니다. 주요 차이점은 PlayerAPlayerB에게 "초대 핸들"을 보내는 대신, PlayerB가 플랫폼 UI에서 진행 중 참가를 시작할 때 "조인 핸들"을 가져온다는 것입니다. 이 "조인 핸들"을 사용하여 PlayerB가 세션 문서에 참가하고 변경 내용을 수신 기다립니다. PlayerA는 새 파티 초대를 만들고 세션 문서에 게시하여 응답합니다. PlayerB는 네트워크 설명자와 함께 새 초대를 확인하고 이 초대를 사용하여 파티 네트워크에 참가합니다.

Important

PartyNetwork::CreateInvitation을 통해 만든 초대는 해당 초대를 만든 PartyLocalUser가 네트워크에서 나가면 무효화됩니다. 따라서 새 사용자는 진행 중 참가 흐름에서 파티 초대를 받더라도 초대를 만든 사용자가 떠나서 초대를 사용할 수 없는 경우 세션 문서에서 자신을 제거하고 나중에 다시 참가하는 것이 좋습니다. 그러면 세션의 다른 멤버가 흐름을 다시 시작하고 이 사용자에 대한 새 파티 초대를 생성할 수 있습니다.

연결 끊김 및 정리

플레이어가 파티 네트워크에서 나가거나 연결이 끊어진 경우 해당 파티 네트워크와 연결된 MPSD 세션에서도 자신을 제거해야 합니다. PartyNetwork::LeaveNetwork 작업에 의해 시작되지 않은 파티 네트워크 연결 끊김은 치명적인 오류로 간주됩니다. 치명적인 연결 끊김이 발생한 후 플레이어가 네트워크에 다시 연결하여 다시 인증할 수 있지만 MPSD 세션에도 다시 참가해야 합니다.

MPSD 세션에 대한 플레이어의 연결이 일시적으로 중단되면 해당 세션에서 연결이 끊어질 수 있습니다. 플레이어는 세션에 다시 참가하려고 시도할 수 있지만 실패할 경우 PartyNetwork::LeaveNetwork를 호출하여 파티 네트워크에서 자신을 자발적으로 제거해야 합니다.

참고 항목

파티 네트워크와 MPSD 세션의 연결 끊김을 감지하는 메커니즘 및 추론은 다릅니다. 플레이어가 파티 네트워크와 MPSD 세션 모두에서 연결이 끊어지더라도 연결 끊김 이벤트는 독립적이며 시간이 지나면서 상호 연결된다는 보장은 없습니다. 타이틀에서는 플레이어가 파티 네트워크 또는 MPSD 세션에서만 연결이 끊어질 수 있는 시나리오를 처리해야 합니다.

게임이 종료되면 플레이어가 파티 네트워크 및 MPSD 문서에서 자동으로 연결 해제되므로 더 이상 정리할 필요가 없습니다.