本主题简要说明如何使用游戏聊天 2 的 C++ API 向游戏添加语音和文本通信功能。

游戏聊天 2 要求你的项目已针对 GDK 进行设置。 有关如何进行设置的详细信息,请参阅 Xbox 主机开发入门(NDA 主题)

编译游戏聊天 2 需要包括 GameChat2.h 主标头。 为了正确链接,项目还必须在至少一个编译单位中包括 GameChat2Impl.h (我们建议使用常见的预编译标头,因为这些存根函数实现很小,容易让编译程序生成为“内联”)。

游戏聊天 2 的界面不需要项目选择是使用 C++/CX 还是传统的 C++ 进行编译。 两种均可用于编译。 实现也不会将引发异常作为非错误报告的方式。 如果愿意,你可以从无异常项目中轻松地使用它。 但实现确实会将引发异常作为错误报告的方式。 (有关详细信息,请参阅本主题后面的 失败模型 部分。)

可以通过初始化游戏聊天 2 单一实例(使用应用于单一实例初始化的生命周期的参数)来开始与库进行交互。 单一实例是通过调用 chat_manager::initialize 进行的初始化,如下所示。

chat_manager::singleton_instance().initialize(...);

必须通过 RegisterAppStateChangeNotification 注册暂停和继续事件。 暂停后,必须使用 chat_manager::cleanup() 清理游戏聊天 2。 恢复后,应重新初始化游戏聊天 2。 如果尝试在暂停/恢复周期中使用它,可能会发生故障。

将用户添加到 Microsoft 游戏开发工具包 (GDK) 游戏

在将用户添加到游戏聊天 2 实例之前,请确保已将用户添加到 GDK 游戏。 这是通过使用 XUserAddAsync API 完成的。 有关此 API 的使用的详细信息,请参阅用户标识和 XUser

拥有希望向游戏聊天 2 添加的用户的 XUserHandle 后,需要使用 XUserGetId API 获取用户的 Xbox 用户 ID (XUID)。 用户必须在线,你必须获得用户的同意才能执行此步骤。

XUserGetId 提供 XUID 作为 uint64_t。 必须将 XUID 转换为 std::wstring,以供用于游戏聊天 2。

下面是一个代码示例,显示在拥有 XUserHandle 后,如何向游戏聊天 2 添加用户。

请注意,调用 XUserResolveIssueWithUiAsync 会显示系统对话框。

HRESULT
AddChatUserFromXUserHandle(
    _In_ XUserHandle user,
    _In_ XTaskQueueHandle queueHandle,
    _Outptr_result_maybenull_ Xs::game_chat_2::chat_user** chatUser
    *chatUser = nullptr;
    uint64_t xuid;
    HRESULT hr = XUserGetId(user, &xuid);
    if (hr == E_GAMEUSER_RESOLVE_USER_ISSUE_REQUIRED)
        XAsyncBlock* asyncBlock = new (std::nothrow) XAsyncBlock;
        if (asyncBlock != nullptr)
            ZeroMemory(asyncBlock, sizeof(*asyncBlock));
            asyncBlock->queue = queueHandle;
            hr = XUserResolveIssueWithUiAsync(user, nullptr, asyncBlock);
            if (SUCCEEDED(hr))
                hr = XAsyncGetStatus(asyncBlock, true);
                if (SUCCEEDED(hr))
                    hr = XUserGetId(user, &xuid);
            delete asyncBlock;
            hr = E_OUTOFMEMORY;
    if (SUCCEEDED(hr))
            std::wstring xuidString = std::to_wstring(xuid);
            // If the user has already been added, this will return the existing user.
            *chatUser = Xs::game_chat_2::chat_manager::singleton_instance().add_local_user(xuidString.c_str());
        catch (const std::bad_alloc&)
            hr = E_OUTOFMEMORY;
    return hr;

向游戏聊天 2 添加用户

将实例初始化后,必须使用 chat_manager::add_local_user 向游戏聊天 2 实例添加本地用户。 在本示例中,用户 A 代表一个本地用户。

chat_user* chatUserA = chat_manager::singleton_instance().add_local_user(<user_a_xuid>);

接下来,添加远程用户以及用于表示用户所在远程终结点的标识符。 终结点是在远程设备上运行的应用的实例。

在本示例中,用户 B 位于终结点 X,用户 C 和 D 均位于终结点 Y。终结点 X 是任意分配的标识符“1”。 终结点 Y 是任意分配的标识符“2”。

通过以下调用将远程用户告诉游戏聊天 2。

chat_user* chatUserB = chat_manager::singleton_instance().add_remote_user(<user_b_xuid>, 1);
chat_user* chatUserC = chat_manager::singleton_instance().add_remote_user(<user_c_xuid>, 2);
chat_user* chatUserD = chat_manager::singleton_instance().add_remote_user(<user_d_xuid>, 2);

接下来,配置每对远程用户和本地用户之间的通信关系。 在本示例中,假设用户 A 和用户 B 处于同一团队中。 允许进行双向通信。 c_communicationRelationshipSendAndReceiveAllGameChat2.h 中定义的常数,用于表示双向通信。

使用 chat_user_local::set_communication_relationship 设置用户 A 与用户 B 之间的关系。

chatUserA->local()->set_communication_relationship(chatUserB, c_communicationRelationshipSendAndReceiveAll);

假设用户 C 和 D 是“观众”,应允许他们听用户 A 说话,但他们自己不能说话。 c_communicationRelationshipSendAllGameChat2.h 中定义的常数,用于表示这个单向通信。

像下面这样设置关系。

chatUserA->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAll);
chatUserA->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAll);

有关来自全部四个本地用户的关系设置的示例,请参阅本主题后面的场景一节。

如果在任何时候有这样的远程用户:已添加到单一实例,但尚未配置为与任何本地用户通信 — 也没问题。 在用户确定团队或可以的任意地更改发言通道的情况下,可能会有这样的用户。

游戏聊天 2 只为已被添加到实例中的用户缓存信息(例如私人关系和信誉),所以将所有可能的用户告诉游戏聊天 2 很有用,— 即使这些用户在某个时间点不能与任何本地用户说话。

最后,假设用户 D 已退出游戏,并且应从本地游戏聊天 2 实例中删除该用户。 可通过使用 chat_manager :: remove_user 完成此操作,如下所示。

chat_manager::singleton_instance().remove_user(chatUserD);

调用 chat_manager::remove_user() 可能会使用户对象无效。 如果你使用的是实时音频操作,请参阅聊天用户生存期,了解详细信息。 否则,在调用 chat_manager::remove_user() 时,用户对象立即失效。 有关何时可删除用户的细微限制的详细信息,请参阅本主题后面的处理状态更改一节。

处理数据帧

游戏聊天 2 本身没有传输层。 必须由应用提供。 该插件由应用对 chat_manager::start_processing_data_frames()chat_manager::finish_processing_data_frames() 方法对的频繁定期调用管理。 游戏聊天 2 正是使用这两种方法向应用提供传出数据。

这些方法旨在快速运行。 可以在专用网络线程上频繁轮询它们。 这就提供了一个便利的位置,用于检索所有排队的数据,而无需担心网络计时的不可预测性或多线程回调的复杂性。

调用 chat_manager::start_processing_data_frames() 后,以 game_chat_data_frame 结构指针数组的形式报告所有排队数据。 应用将循环访问该数组,检查目标终结点,然后使用应用的网络层将数据传送给适当的远程应用实例。

使用所有 game_chat_data_frame 结构完成数组后,应通过调用 chat_manager:finish_processing_data_frames() 将数组传回游戏聊天 2,以便释放资源。 下面的示例演示了此操作。

uint32_t dataFrameCount;
game_chat_data_frame_array dataFrames;
chat_manager::singleton_instance().start_processing_data_frames(&dataFrameCount, &dataFrames);
for (uint32_t dataFrameIndex = 0; dataFrameIndex < dataFrameCount; ++dataFrameIndex)
    game_chat_data_frame const* dataFrame = dataFrames[dataFrameIndex];
    // Title-written function responsible for sending packet to remote instances of GameChat 2.
    HandleOutgoingDataFrame(
        dataFrame->packet_byte_count,
        dataFrame->packet_buffer,
        dataFrame->target_endpoint_identifier_count,
        dataFrame->target_endpoint_identifiers,
        dataFrame->transport_requirement
chat_manager::singleton_instance().finish_processing_data_frames(dataFrames);

处理数据帧的频率越高,用户明显感到的音频延迟就越低。 音频被合并到 40 毫秒的数据帧中。 这就是建议的轮询周期。

处理状态更改

游戏聊天 2 通过应用对 chat_manager::start_processing_state_changes()chat_manager::finish_processing_state_changes() 方法对的定期且频繁的调用来提供对应用的更新,例如收到的文本消息。 这些方法快速运作,以便可以在 UI 呈现循环中每个图形帧都调用它们。 这就提供了一个便利的位置,用于检索所有排队的更改,而无需担心网络计时的不可预测性或多线程回调的复杂性。

调用 chat_manager::start_processing_state_changes() 后,以 game_chat_state_change 结构指针数组的形式报告所有排队的更新。 应用应迭代该数组,并检查更具体类型的基本结构,将基本结构转换为相应更详细的类型,然后根据需要处理更新。

使用所有当前可用的 game_chat_state_change 对象完成数组后,应通过调用 chat_manager::finish_processing_state_changes() 将数组传回游戏聊天 2,以便释放资源。 下面的示例演示了此操作。

uint32_t stateChangeCount;
game_chat_state_change_array gameChatStateChanges;
chat_manager::singleton_instance().start_processing_state_changes(&stateChangeCount, &gameChatStateChanges);
std::list<Xs::game_chat_2::chat_user*> usersWithPrivilegeIssues;
std::list<Xs::game_chat_2::chat_user*> usersWithPrivilegeCheckIssues;
for (uint32_t stateChangeIndex = 0; stateChangeIndex < stateChangeCount; ++stateChangeIndex)
    switch (gameChatStateChanges[stateChangeIndex]->state_change_type)
        case game_chat_state_change_type::text_chat_received:
            HandleTextChatReceived(static_cast<const game_chat_text_chat_received_state_change*>(gameChatStateChanges[stateChangeIndex]));
            break;
        case Xs::game_chat_2::game_chat_state_change_type::transcribed_chat_received:
            HandleTranscribedChatReceived(static_cast<const Xs::game_chat_2::game_chat_transcribed_chat_received_state_change*>(gameChatStateChanges[stateChangeIndex]));
            break;
        case Xs::game_chat_2::game_chat_state_change_type::communication_relationship_adjuster_changed:
            HandleAdjusterChangedStateReceived(static_cast<const Xs::game_chat_2::game_chat_communication_relationship_adjuster_changed_state_change*>(gameChatStateChanges[stateChangeIndex]), usersWithPrivilegeIssues, usersWithPrivilegeCheckIssues);
            break;
chat_manager::singleton_instance().finish_processing_state_changes(gameChatStateChanges);

因为 chat_manager::remove_user() 会使与用户对象关联的内存立即失效,且状态更改可能包含用户对象指针,因此处理状态更改时不得调用 chat_manager::remove_user()

如果要发送文字聊天,请使用 chat_user::chat_user_local::send_chat_text()。 下面的示例演示了此操作。

chatUserA->local()->send_chat_text(L"Hello");

游戏聊天 2 生成了包含此消息的数据帧。 数据帧的目标终结点与已配置为接收来自本地用户的文本的用户相关联。 远程终结点处理数据时,通过 game_chat_text_chat_received_state_change 公开消息。

与语音聊天一样,文本聊天也需符合特权和隐私限制。 如果已将一对用户配置为允许文本聊天,但特权或隐私限制禁止该通信,则将删除文本消息。

辅助功能要求支持文字聊天输入和显示。

文本输入是必需的,因为即使在以往未普遍使用物理键盘的平台或游戏类型上,用户也可以将系统配置为使用文本到语音转换辅助技术。

类似地,文本显示也是必需的,因为用户可能会将系统配置为使用语音转文本。

可以通过分别调用 chat_user::chat_user_local::text_to_speech_conversion_preference_enabled()chat_user::chat_user_local::speech_to_text_conversion_preference_enabled() 方法检测本地用户的这些首选项。 我们建议根据用户首选项有条件地启用文本。

文本转语音

如果用户启用了文本转语音,chat_user::chat_user_local::text_to_speech_conversion_preference_enabled() 返回 true。 检测到此状态时,应用必须提供一种文本输入方法。

通过真实或虚拟键盘提供文本输入后,请将字符串传递给 chat_user::chat_user_local::synthesize_text_to_speech() 方法。 游戏聊天 2 基于该字符串以及用户的辅助功能语音首选项检测和合成音频数据。 下面的示例演示了此操作。

chat_userA->local()->synthesize_text_to_speech(L"Hello");

此操作过程中合成的音频被传输给配置为从该本地用户接收音频的所有用户。 如果对未启用文本转语音功能的用户调用 chat_user::chat_user_local::synthesize_text_to_speech(),游戏聊天 2 不执行任何操作。

语音转文本

如果用户启用了语音转文本,chat_user::chat_user_local::speech_to_text_conversion_preference_enabled() 返回 true。 检测到此状态时,应用必须已就绪,以提供与转录的聊天消息关联的 UI。 游戏聊天 2 自动转录每个远程用户的音频,并通过 game_chat_transcribed_chat_received_state_change 结构公开它。

语音转文本性能注意事项

启用语音转文本后,每台远程设备上的游戏聊天 2 实例发起与语音服务终结点的 Web 套接字连接。 每个游戏聊天 2 远程客户端通过此 web 套接字将音频上传到语音服务终结点。 语音服务终结点偶尔向远程设备返回一条转录消息。 然后,远程设备将转录消息(即文本消息)发送到本地设备。 游戏聊天 2 将转录的消息提供给应用进行呈现。

因此,语音转文本的主要性能成本是网络使用量。 大部分网络流量都是上传编码音频。 在普通语音聊天路径中,Web 套接字上传已由游戏聊天 2 编码的音频。 应用通过 chat_manager::set_audio_encoding_bitrate 控制比特率。

建议在任何显示用户 UI 的位置(尤其是在记分板等显示一列玩家代号的位置中)也显示静音/说话图标,作为对用户的反馈。 实现这一点的方式是:调用 chat_user::chat_indicator() 来检索表示该用户当前瞬时聊天状态的 game_chat_user_chat_indicator 枚举。 以下示例展示了如何检索由变量 chatUserA 指向的 chat_user 对象的指标值,以确定要分配给 iconToShow 变量的特定图标常量值。

switch (chatUserA->chat_indicator())
   case game_chat_user_chat_indicator::silent:
       iconToShow = Icon_InactiveSpeaker;
       break;
   case game_chat_user_chat_indicator::talking:
       iconToShow = Icon_ActiveSpeaker;
       break;
   case game_chat_user_chat_indicator::local_microphone_muted:
       iconToShow = Icon_MutedSpeaker;
       break;

例如,chat_user::chat_indicator() 报告的值应随着玩家开始和停止说话而频繁变化。 因此,该值被设计为支持每个 UI 帧都对它进行轮询的应用。

可以使用 chat_user::chat_user_local::set_microphone_muted() 方法切换本地用户的麦克风静音状态。 将麦克风静音后,不捕获来自该麦克风的音频。 如果用户在 Kinect 等共享设备上,则静音状态应用于所有用户。

可以使用 chat_user::chat_user_local::microphone_muted() 方法检索本地用户的麦克风静音状态。 此方法通过调用 chat_user::chat_user_local::set_microphone_muted(),只反映是否已在软件中将本地用户的麦克风静音。 不反映由硬件控制的静音,例如通过用户耳机上的按钮禁音。

没有通过游戏聊天 2 检索用户音频设备的硬件静音状态的方法。

可以使用 chat_user::chat_user_local::set_remote_user_muted() 方法切换远程用户对于某个本地用户的静音状态。 远程用户静音后,本地用户不会听到来自该远程用户的任何音频或接收到来自他的任何文本消息。

不良信誉自动静音

远程用户一开始并未静音。 游戏聊天 2 在以下情况下开始将用户禁音:

  • 远程用户不是本地用户的好友。
  • 远程用户拥有不良信誉标志。
  • 用户因为此操作而被静音时,chat_user::chat_indicator() 返回 game_chat_user_chat_indicator::reputation_restricted。 此状态将由对 chat_user::chat_user_local::set_remote_user_muted()(其中将远程用户作为目标用户)的首次调用替代。

    特权和隐私

    除了由游戏配置的通信关系之外,游戏聊天 2 还强制执行特权和隐私限制。 首次添加用户时,游戏聊天 2 执行特权和隐私限制查找。 用户的 chat_user::chat_indicator() 始终返回 game_chat_user_chat_indicator::silent,直到这些操作完成。

    如果与用户的通信受到特权或隐私限制的影响,则用户的 chat_user::chat_indicator() 返回 game_chat_user_chat_indicator::platform_restricted。 平台通信限制对语音聊天和文字聊天都适用。 绝不会有平台限制阻止文字聊天而不阻止语音聊天的实例,反之亦然。

    chat_user::chat_user_local::get_effective_communication_relationship() 可用于帮助区分用户因特权和隐私操作不完整而无法通信的情况。 它返回由游戏聊天 2 强制施加的通信关系(采用 game_chat_communication_relationship_flags 形式),以及该关系可能不等于配置的关系(采用 game_chat_communication_relationship_adjuster 枚举的形式)的原因。

    例如,如果查找操作仍在进行,game_chat_communication_relationship_adjuster 将为 game_chat_communication_relationship_adjuster::initializing。 不应使用此方法来影响 UI。 (有关详细信息,请参阅本主题前面的 UI 一节。)

    如果游戏聊天 2 遇到特权问题,将在 communication_relationship_adjuster_changed 状态更改中报告该问题。

    如果游戏聊天 2 因不可恢复的原因未能检索用户的特权,系统会将该问题报告为 game_chat_communication_relationship_adjuster::privilege_check_failure 调节器。

    如果游戏聊天 2 因用户也许能解决的问题而未能检索用户的特权,系统会将该问题报告为 game_chat_communication_relationship_adjuster::resolve_user_issue 调节器。

    如果用户缺少可能可以通过 UI 解析的特权,系统会将该问题报告为 game_chat_communication_relationship_adjuster::privilege 调节器。

    在这些情况下,通信将受到限制。

    下面的示例说明如何检查用户是否存在以下某一个常见问题。

  • 用户需要对 Xbox Live 予以准许,以便游戏聊天 2 检查特权。
  • 将用户帐户配置为拒绝特权(例如,配置为子帐户,使其无法使用聊天功能)。
  • HandleAdjusterChangedStateReceived ( _In_ const Xs::game_chat_2::game_chat_communication_relationship_adjuster_changed_state_change* adjusterChange, _Inout_ std::list<Xs::game_chat_2::chat_user*>& usersWithPrivilegeIssues, _Inout_ std::list<Xs::game_chat_2::chat_user*>& usersWithPrivilegeCheckIssues Xs::game_chat_2::game_chat_communication_relationship_flags communicationRelationship; Xs::game_chat_2::game_chat_communication_relationship_adjuster communicationRelationshipAdjuster; adjusterChange->local_user->local()->get_effective_communication_relationship( adjusterChange->target_user, &communicationRelationship, &communicationRelationshipAdjuster); if (communicationRelationshipAdjuster == Xs::game_chat_2::game_chat_communication_relationship_adjuster::privilege) // The local user has privilege issues. usersWithPrivilegeIssues.push_back(adjusterChange->local_user); else if (communicationRelationshipAdjuster == Xs::game_chat_2::game_chat_communication_relationship_adjuster::resolve_user_issue) // The local user has an issue checking privileges. usersWithPrivilegeCheckIssues.push_back(adjusterChange->local_user);

    对于使用 game_chat_communication_relationship_adjuster::privilege 调节器报告的问题,可以使用 XUserPrivilegeOptions::NoneXUserPrivilege::Communications 调用 XUserResolvePrivilegeWithUiAsync,以尝试解决该问题。

    对于通过 game_chat_communication_relationship_adjuster::resolve_user_issue 调节器报告的问题,可以通过 nullptr 对 URL 调用 XUserResolveIssueWithUiAsync 来尝试解决此问题。

    我们建议你显示指示存在特权问题的 UI。 允许用户决定是否要尝试通过按下按钮或菜单选项来解决该问题。

    用户可能无法或不愿意解决此问题。 如果用户确实解决了此问题,会在下一次将用户添加到游戏聊天 2 时生效。

    在处理状态更改时(也就是调用 chat_manager::start_processing_state_changes() 后以及对 chat_manager::finish_processing_state_changes() 的相应调用前)不得调用 chat_manager::remove_user()。 在正在处理状态更改时调用 chat_manager\::remove_user() 会使与删除的这位用户相关联的内存无效。 如果你看到 game_chat_communication_relationship_adjuster::privilege 调节器并且希望尝试解析用户特权,应等到处理状态更改后再尝试此操作。

    若要从 XUID 获取调用 XUserResolvePrivilegeWithUiAsync 所必需的 XUserHandle,可以使用 XUserFindUserById API 获取新的 XUserHandle。 或者,可以继续使用通过 XUserAddAsync 获取的项,并跟踪映射到它的 XUID。

    下面的示例展示如何解决这些问题。

    // If we got an Xs::game_chat_2::game_chat_communication_relationship_adjuster::resolve_user_issue,
    // we need to try and fix our issue, if we haven't already, and then remove and re-add that user.
    for (Xs::game_chat_2::chat_user* localUser : usersWithPrivilegeCheckIssues)
        auto asyncBlock = std::make_unique<XAsyncBlock>();
        ZeroMemory(asyncBlock.get(), sizeof(*asyncBlock));
        asyncBlock->queue = g_asyncQueue;
        XUserHandle userHandle;
        hr = XUserFindUserById(localUser->local()->xbox_user_id(), &userHandle);
        if (SUCCEEDED(hr))
            hr = XUserResolveIssueWithUiAsync(
                userHandle,
                nullptr,
                asyncBlock.get());
            if (SUCCEEDED(hr))
                hr = XAsyncGetStatus(asyncBlock.get(), true);
                if (SUCCEEDED(hr))
                    // Remove and re-add the user after fixing the privileges.
                    // Users must not be removed while processing state changes.
                asyncBlock.release();
    // If we got an Xs::game_chat_2::game_chat_communication_relationship_adjuster::resolve_user_privilege,
    // we need to try and resolve the privileges, if we haven't already, and then remove and re-add that user.
    for (Xs::game_chat_2::chat_user* localUser : usersWithPrivilegeIssues)
        auto asyncBlock = std::make_unique<XAsyncBlock>();
        ZeroMemory(asyncBlock.get(), sizeof(*asyncBlock));
        asyncBlock->queue = g_asyncQueue;
        XUserHandle userHandle;
        hr = XUserFindUserById(localUser->local()->xbox_user_id(), &userHandle);
        if (SUCCEEDED(hr))
            hr = XUserResolvePrivilegeWithUiAsync(
                userHandle,
                XUserPrivilegeOptions::None,
                XUserPrivilege::Communications,
                asyncBlock.get());
            if (SUCCEEDED(hr))
                hr = XAsyncGetStatus(asyncBlock.get(), true);
                if (SUCCEEDED(hr))
                    // Remove and re-add the user after fixing the privileges.
                    // Users must not be removed while processing state changes.
                asyncBlock.release();
    

    当应用不再需要通过游戏聊天 2 进行通信时,应该调用 chat_manager::cleanup()。 这使游戏聊天 2 能够回收已分配的资源,以管理通信。

    游戏聊天 2 实现不会将引发异常作为非错误报告的方式。 如果愿意,你可以从无异常项目中轻松地使用它。 但,游戏聊天 2 确实会引发异常,告诉你发生错误。

    这些错误是误用 API 的结果,例如在初始化实例前将用户添加到游戏聊天实例,或者从游戏聊天 2 实例中删除用户对象后访问该用户对象。

    预计在开发过程的早期捕获这些错误,并且可通过修改用于与游戏聊天 2 交互的模式来更正这些错误。 发生此类错误时,系统在引发异常前,向调试程序发送一个提示,说明导致该错误的原因。

    如何配置热门场景

    按下即可发言

    应使用 chat_user::chat_user_local::set_microphone_muted() 实现按下即可发言。 调用 set_microphone_muted(false) 允许使用语音,调用 set_microphone_muted(true) 对其进行限制。 如果使用此方法,来自游戏聊天 2 的响应的延迟最低。

    假设用户 A 和用户 B 都在蓝队中,并且用户 C 和用户 D 都在红队中。 每个用户都是应用的唯一实例。

    在用户 A 的设备上:

    chatUserA->local()->set_communication_relationship(chatUserB, c_communicationRelationshipSendAndReceiveAll);
    chatUserA->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);
    chatUserA->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
    

    在用户 B 的设备上:

    chatUserB->local()->set_communication_relationship(chatUserA, c_communicationRelationshipSendAndReceiveAll);
    chatUserB->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);
    chatUserB->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
    

    在用户 C 的设备上:

    chatUserC->local()->set_communication_relationship(chatUserA, game_chat_communication_relationship_flags::none);
    chatUserC->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
    chatUserC->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAndReceiveAll);
    

    在用户 D 的设备上:

    chatUserD->local()->set_communication_relationship(chatUserA, game_chat_communication_relationship_flags::none);
    chatUserD->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
    chatUserD->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAndReceiveAll);
    

    假设用户 A 是领导,发号施令。 用户 B、C、D 只能听。 每个玩家各在一台设备上。

    在用户 A 的设备上:

    chatUserA->local()->set_communication_relationship(chatUserB, c_communicationRelationshipSendAll);
    chatUserA->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAll);
    chatUserA->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAll);
    

    在用户 B 的设备上:

    chatUserB->local()->set_communication_relationship(chatUserA, c_communicationRelationshipReceiveAll);
    chatUserB->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);
    chatUserB->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
    

    在用户 C 的设备上:

    chatUserC->local()->set_communication_relationship(chatUserA, c_communicationRelationshipReceiveAll);
    chatUserC->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
    chatUserC->local()->set_communication_relationship(chatUserD, game_chat_communication_relationship_flags::none);
    

    在用户 D 的设备上:

    chatUserD->local()->set_communication_relationship(chatUserA, c_communicationRelationshipReceiveAll);
    chatUserD->local()->set_communication_relationship(chatUserB, game_chat_communication_relationship_flags::none);
    chatUserD->local()->set_communication_relationship(chatUserC, game_chat_communication_relationship_flags::none);
    

    游戏聊天 2 简介

    实时音频操作

    API 内容 (GameChat2)

    Microsoft 游戏开发工具包