将 PlayFab Party 与 MPSD 结合使用

Xbox 多人游戏场景依赖于多人游戏会话目录(MPSD)服务和 MPSD 文档结合使用。 MPSD 文档充当当前游戏会话的名单并驱动多人游戏体验,例如匹配、平台邀请、最近玩家列表以及正在加入。

在本文档中,我们将介绍如何将 PlayFab Party 合并到需要 MPSD 的常见多人游戏流程中。

本文档不提供 MPSD 及其所有功能的深入讨论。 有关详细信息,请参阅 MPSD 文档

匹配

以下是介绍如何将匹配和 MPSD 与 PlayFab Party 结合使用的简化流程:

  1. 玩家将创建并聚集到 MPSD 会话,这些会话代表他们希望跨匹配会话一同畅玩的组。 玩家将使用 Xbox 的邀请和加入功能聚集到这些会话。

  2. 这些玩家组将向匹配服务提交票证,该服务会将兼容的玩家组聚集到匹配会话。 此匹配会话本身将由玩家随后将加入的新会话文档表示。 此外,玩家还必须侦听对此会话文档的更改。

  3. 匹配会话完成且名单锁定后,游戏必须选择匹配会话的一位成员以设置 PlayFab Party 网络。 选择 Party 网络创建者的简单策略是使用匹配 MPSD 会话文档的第一个成员。

  4. 所选成员将使用初始 PartyInvitation 创建网络,将网络访问权限限制为仅匹配会话的成员。 网络成功完成创建后,所选成员应将生成的网络描述符和 Party 邀请作为会话属性发布到会话文档,以供其他成员使用。

    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 Party 合并的流程。 此流程的核心理念可以扩展到你可能对 MPSD 感兴趣的其他流程,但本文档不提供所有可能的流程。 有关详细信息,请参阅 完整的 MPSD 文档

平台邀请

以下是如何将 Xbox 平台邀请合并到 PlayFab Party 的流程:

  1. PlayerA 创建 MPSD 会话文档、侦听会话更改并创建 Party 网络。 完成 Party 网络创建后,*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. PlayerA 希望邀请 PlayerB 加入 Party 网络时,PlayerA 会通过游戏内或控制台 UI 向 PlayerB 发起平台邀请。

  3. PlayerB 收到平台邀请,其中包括 PlayerB 可用于查找 PlayerA MPSD 会话文档的“邀请句柄”。

  4. PlayerB 加入会话文档并侦听更改。

  5. PlayerA 看到 PlayerB 加入会话文档。 PlayerA 新建邀请以供 PlayerB 使用,并将该邀请发布到会话文档

    void
    OnUserJoinedSessionDocument(
        PartyNetwork* network,
        uint64_t newSessionMemberXboxUserId
        )
    {
        std::string newMemberIdString = std::to_string(newSessionMemberXboxUserId);
    
        // Specify our own invitation id so we don't have to query for it after the invitation has been created.
        // Here we will specify the invite id with the format "InviterXboxUserID_InviteeXboxUserID" so that we can
        // ensure this invitation ID doesn't clash with the invitations other members might try and create for this user.
        std::string invitationId = std::to_string(m_localXboxUserId) + "_" + newMemberIdString;
    
        PartyInvitationConfiguration newInvite{};
        newInvite.identifier = invitationId.c_str();
        newInvite.revocability = PartyInvitationRevocability::Creator; // must be revocable by the creator only
    
        // Call title-defined xuid->entityid mapping helper
        PartyString newSessionMemberEntityId = GetEntityIdFromXboxUserId(newSessionMemberXboxUserId);
        newInvite.entityIdCount = 1;
        newInvite.entityIds = &newSessionMemberEntityId;
    
        // Create a new invitation which includes all of the users currently in the document
        PartyInvitation* newInvitation;
        PartyError error = network->CreateInvitation(
            m_localUser,
            &newInvite,
            nullptr,
            &newInvitation);
        if (PARTY_FAILED(error))
        {
            DEBUGLOG("PartyNetwork(0x%p)::CreateInvitation failed! (error=0x%x)", network, error);
            return;
        }
    
        // Post the invitation to the local user's member property store in the session document, key'd by the invitee's
        // xbox user id. This will let the invitee recognize when an invitation is intended for them.
        UpdateMemberProperty(
            newMemberIdString.c_str(),
            invitationId.c_str());
    }
    
  6. PlayerB 看到发布到会话文档的邀请,并使用邀请加入 Party 网络。

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

    重要

    如果创建邀请的 PartyLocalUser 离开网络,则通过 PartyNetwork::CreateInvitation 创建的邀请将失效。 因此,如果新用户将自己添加到会话文档,但邀请他们的用户已离开,则我们建议新用户将自己从会话文档中删除,并等待另一用户重新邀请。

正在加入

正在加入游戏会话非常类似于 平台邀请 场景。 核心区别在于,PlayerA 不会向 PlayerB 发送“邀请句柄”,而是 PlayerB 将在从平台 UI 发起正在加入时获得“加入句柄”。 使用此“加入句柄”,PlayerB 将加入会话文档并侦听更改。 PlayerA 将做出响应,为他们新建 Party 邀请并将其发布到会话文档。 PlayerB 将在网络描述符旁看到此新邀请,并使用该邀请加入 Party 网络。

重要

如果创建邀请的 PartyLocalUser 离开网络,则通过 PartyNetwork::CreateInvitation 创建的邀请将失效。 因此,如果新用户收到来自正在加入流程的 Party 邀请,但该邀请却由于创建它的用户已离开而无法使用,则我们建议新用户将自己从会话文档中删除,并稍后重新加入。 这将允许会话的另一成员重启流程并为此用户生成新的 Party 邀请。

断开连接和清理

如果玩家离开或以其他方式与 Party 网络断开连接,他们还应将自己从与该 Party 网络关联的 MPSD 会话中删除。 不是由 PartyNetwork::LeaveNetwork 操作发起的 Party 网络断开连接被视为严重错误。 在遇到严重的断开连接后,玩家可能会尝试重新连接并重新验证到网络,但还必须重新加入 MPSD 会话。

如果玩家与 MPSD 会话的连接暂时中断,则他们可能会与该会话断开连接。 玩家可以尝试重新加入会话,但如果失败,则应调用 PartyNetwork::LeaveNetwork,自愿将自己从 Party 网络中删除。

注意

检测 Party 网络和 MPSD 会话断开连接的机制和启发法并不相同。 即使在玩家将同时与 Party 网络和 MPSD 会话断开连接的场景中,这些断开连接事件也为独立事件,且无法保证它们发生的时间互相接近。 游戏应处理玩家可能只与 Party 网络或 MPSD 会话断开连接的场景。

如果游戏关闭,则玩家将自动与 Party 网络和 MPSD 文档断开连接,无需进一步清理。