> ## 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.

# 使用 Game Chat 2 C++ API

> XBOX GDK 上 Game Chat 2 C++ API 演练,包括初始化、添加用户、配置聊天频道和错误处理模式。

本主题简要介绍如何使用 Game Chat 2 的 C++ API 为游戏添加语音和文本通信。

## 先决条件

Game Chat 2 要求您的项目已经为 GDK 进行了设置。有关如何设置的详细信息,请参见 [开始使用 Microsoft Game Development Kit](https://learn.microsoft.com/gaming/gdk/docs/services/gdk-dev/get-started/get-started-home)。

编译 Game Chat 2 需要包含主要的 *GameChat2.h* 头文件。
为了正确链接,您的项目还必须在至少一个编译单元中包含 *GameChat2Impl.h*(我们建议使用共同的预编译头,因为这些存根函数实现很小,编译器容易将它们生成为 "内联")。

Game Chat 2 接口不要求项目在使用 C++/CX 与传统 C++ 编译之间做出选择。它可以与两者一起使用。实现也不会抛出异常作为非致命错误报告的手段。如果您愿意,可以轻松地从无异常的项目中使用它。但是,该实现会抛出异常作为致命错误报告的手段。(有关更多详细信息,请参见本主题稍后的 [失败模型](/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2#FAILURE_MODEL) 部分。)

## 初始化

通过使用适用于单例初始化生命周期的参数初始化 Game Chat 2 单例实例,开始与库交互。通过调用 [chat\_manager::initialize](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_initialize) 初始化单例实例,如下所示。

```cpp theme={null}
chat_manager::singleton_instance().initialize(...);
```

<Note>您必须通过 `RegisterAppStateChangeNotification` 注册挂起和恢复事件。挂起时,必须使用 [chat\_manager::cleanup()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_cleanup) 清理 Game Chat 2。恢复时,应重新初始化 Game Chat 2。如果尝试在挂起/恢复周期中使用它,它可能会崩溃。</Note>

<a id="configuring_users" />

## 配置用户

### 将用户添加到 Microsoft Game Development Kit (GDK) 游戏

在将用户添加到 Game Chat 2 实例之前,请确保它们已添加到 GDK 游戏中。
这是通过使用 [XUserAddAsync API](/reference/system/xuser/functions/xuseraddasync) 完成的。有关使用此 API 的更多信息,请参见 [用户身份和 XUser](/build/core-features/common/user/player-identity-xuser)。

获得要添加到 Game Chat 2 的用户的 `XUserHandle` 之后,您需要使用 [XUserGetId API](/reference/system/xuser/functions/xusergetid) 获取用户的 XBOX 用户 ID (XUID)。
用户必须在线,并且您必须获得用户对此步骤的同意。

[XUserGetId](/reference/system/xuser/functions/xusergetid) 以 `uint64_t` 形式提供 XUID。您必须将 XUID 转换为 `std::wstring` 才能与 Game Chat 2 一起使用。

以下是一个代码示例,演示了如何在拥有 `XUserHandle` 之后将用户添加到 Game Chat 2。
<Note>请注意,调用 [XUserResolveIssueWithUiAsync](/reference/system/xuser/functions/xuserresolveissuewithuiasync) 会显示系统对话框。</Note>

```cpp theme={null}
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;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if (SUCCEEDED(hr))
    {
        try
        {
            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;
}
```

<a id="adding_users_to_game_chat_2" />

### 将用户添加到 Game Chat 2

初始化实例后,必须使用 [chat\_manager::add\_local\_user](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_add_local_user) 将本地用户添加到 Game Chat 2 实例。在此示例中,用户 A 代表本地用户。

```cpp theme={null}
chat_user* chatUserA = chat_manager::singleton_instance().add_local_user(<user_a_xuid>);
```

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

在此示例中,用户 B 在端点 X 上。用户 C 和 D 在端点 Y 上。
端点 X 任意分配标识符 "1"。端点 Y 任意分配标识符 "2"。

通过以下调用告知 Game Chat 2 有关远程用户的信息。

```cpp theme={null}
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_communicationRelationshipSendAndReceiveAll` 是 *GameChat2.h* 中定义的常量,用于表示双向通信。

使用 [chat\_user\_local::set\_communication\_relationship](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_set_communication_relationship) 设置用户 A 与用户 B 的关系。

```cpp theme={null}
chatUserA->local()->set_communication_relationship(chatUserB, c_communicationRelationshipSendAndReceiveAll);
```

假设用户 C 和 D 是 "观众",应允许他们听到用户 A 的声音但不能说话。
`c_communicationRelationshipSendAll` 是 *GameChat2.h* 中定义的常量,用于表示这种单向通信。

按如下方式设置关系。

```cpp theme={null}
chatUserA->local()->set_communication_relationship(chatUserC, c_communicationRelationshipSendAll);
chatUserA->local()->set_communication_relationship(chatUserD, c_communicationRelationshipSendAll);
```

有关所有四个本地用户关系设置的示例,请参见本主题稍后的 [场景](/services/xbox-services/multiplayer/chat/game-chat2/using-game-chat-2#scenarios) 部分。

如果在任何时候有远程用户被添加到单例实例但未配置为与任何本地用户通信 — 这没关系。
这在用户决定队伍或可以任意更改讲话频道的场景中可能是预期的。

Game Chat 2 仅缓存已添加到实例的用户的信息(例如隐私关系和信誉),因此告知 Game Chat 2 所有可能的用户很有用 — 即使他们在特定时间点不能与任何本地用户交谈。

最后,假设用户 D 离开了游戏,应从本地 Game Chat 2 实例中删除。
可以使用 [chat\_manager::remove\_user](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_remove_user) 完成,如下所示。

```cpp theme={null}
chat_manager::singleton_instance().remove_user(chatUserD);
```

调用 `chat_manager::remove_user()` 可能使用户对象无效。如果您使用 [实时音频处理](/services/xbox-services/multiplayer/chat/game-chat2/real-time-audio-manipulation),请参阅 [聊天用户生命周期](/services/xbox-services/multiplayer/chat/game-chat2/real-time-audio-manipulation#chat-user-lifetimes) 了解更多信息。否则,在调用 `chat_manager::remove_user()` 时,用户对象立即失效。有关何时可以删除用户的细微限制在本主题稍后的 [处理状态更改](#processing-state-changes) 部分详细介绍。

<a id="processing_data_frames" />

## 处理数据帧

Game Chat 2 没有自己的传输层。它必须由应用提供。
此插入通过应用定期、频繁地调用 [chat\_manager::start\_processing\_data\_frames()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_start_processing_data_frames) 和 [chat\_manager::finish\_processing\_data\_frames()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_finish_processing_data_frames) 这对方法来管理。这些方法是 Game Chat 2 向应用提供输出数据的方式。

这些方法设计为快速操作。可以在专用网络线程上频繁轮询它们。
这为检索所有排队数据提供了方便的位置,而不必担心网络时序的不可预测性或多线程回调的复杂性。

当调用 `chat_manager::start_processing_data_frames()` 时,所有排队的数据都在 [game\_chat\_data\_frame](/reference/chat/gamechat2/structs/game_chat_data_frame) 结构指针数组中报告。
应用应遍历数组,检查目标端点,并使用应用的网络层将数据传送到适当的远程应用实例。

在完成数组中所有 [game\_chat\_data\_frame](/reference/chat/gamechat2/structs/game_chat_data_frame) 结构的处理后,应通过调用 `chat_manager:finish_processing_data_frames()` 将数组传回 Game Chat 2 以释放资源。
这在以下示例中显示。

```cpp theme={null}
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 ms 数据帧。这是建议的轮询周期。

<a id="processing-state-changes" />

## 处理状态更改

Game Chat 2 通过应用定期、频繁调用 [chat\_manager::start\_processing\_state\_changes()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_start_processing_state_changes) 和 [chat\_manager::finish\_processing\_state\_changes()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_finish_processing_state_changes) 这对方法向应用提供更新,例如收到的文本消息。
这些方法操作迅速,因此可以在您的 UI 渲染循环中的每个图形帧调用它们。
这为检索所有排队更改提供了方便的位置,而不必担心网络时序的不可预测性或多线程回调的复杂性。

当调用 `chat_manager::start_processing_state_changes()` 时,所有排队的更新都在 [game\_chat\_state\_change](/reference/chat/gamechat2/structs/game_chat_state_change) 结构指针数组中报告。
应用应遍历数组,检查基础结构的具体类型,将基础结构强制转换为相应的更详细类型,然后适当地处理该更新。

在完成当前所有可用的 [game\_chat\_state\_change](/reference/chat/gamechat2/structs/game_chat_state_change) 对象处理后,应通过调用 `chat_manager::finish_processing_state_changes()` 将数组传回 Game Chat 2 以释放资源。
这在以下示例中显示。

```cpp theme={null}
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()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_send_chat_text)。
这在以下示例中显示。

```cpp theme={null}
chatUserA->local()->send_chat_text(L"Hello");
```

Game Chat 2 生成包含此消息的数据帧。数据帧的目标端点是与已配置为接收本地用户文本的用户关联的端点。
当数据被远程端点处理时,消息通过 [game\_chat\_text\_chat\_received\_state\_change](/reference/chat/gamechat2/structs/game_chat_text_chat_received_state_change) 公开。

与语音聊天一样,文本聊天也遵守特权和隐私限制。
如果一对用户已配置为允许文本聊天,但特权或隐私限制不允许该通信,则文本消息将被丢弃。

## 无障碍功能

无障碍功能要求支持文本聊天输入和显示。

需要文本输入是因为,即使在历史上没有广泛使用物理键盘的平台或游戏类型,用户也可以配置系统以使用文本转语音辅助技术。

同样,需要文本显示是因为用户可以配置系统以使用语音转文本。

可以通过分别调用 [chat\_user::chat\_user\_local::text\_to\_speech\_conversion\_preference\_enabled()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_text_to_speech_conversion_preference_enabled) 和 [chat\_user::chat\_user\_local::speech\_to\_text\_conversion\_preference\_enabled()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_speech_to_text_conversion_preference_enabled) 方法检测本地用户的这些偏好。我们建议您根据用户偏好有条件地启用文本。

### 文本转语音

当用户启用了文本转语音时,[chat\_user::chat\_user\_local::text\_to\_speech\_conversion\_preference\_enabled()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_text_to_speech_conversion_preference_enabled) 返回 `true`。检测到此状态时,应用必须提供文本输入方法。

获得由真实或虚拟键盘提供的文本输入后,将字符串传递给 [chat\_user::chat\_user\_local::synthesize\_text\_to\_speech()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_synthesize_text_to_speech) 方法。Game Chat 2 根据字符串和用户的无障碍语音偏好检测和合成音频数据。
这在以下示例中显示。

```cpp theme={null}
chat_userA->local()->synthesize_text_to_speech(L"Hello");
```

作为此操作一部分合成的音频将传输到已配置为接收此本地用户音频的所有用户。
如果对未启用文本转语音的用户调用 `chat_user::chat_user_local::synthesize_text_to_speech()`,则 Game Chat 2 不采取任何操作。

### 语音转文本

当用户启用了语音转文本时,[chat\_user::chat\_user\_local::speech\_to\_text\_conversion\_preference\_enabled()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_speech_to_text_conversion_preference_enabled) 返回 `true`。检测到此状态时,应用必须准备提供与转录聊天消息相关的 UI。Game Chat 2 自动转录每个远程用户的音频,并通过 [game\_chat\_transcribed\_chat\_received\_state\_change](/reference/chat/gamechat2/structs/game_chat_transcribed_chat_received_state_change) 结构公开。

### 语音转文本性能注意事项

启用语音转文本时,每个远程设备上的 Game Chat 2 实例与语音服务端点启动 WebSocket 连接。
每个远程 Game Chat 2 客户端通过此 WebSocket 将音频上传到语音服务端点。语音服务端点偶尔向远程设备返回转录消息。
然后远程设备将转录消息(即文本消息)发送到本地设备。转录的消息由 Game Chat 2 提供给应用进行渲染。

因此,语音转文本的主要性能成本是网络使用。
大部分网络流量是编码音频的上传。
WebSocket 上传的音频已经在 "正常" 语音聊天路径中由 Game Chat 2 编码。应用通过 [chat\_manager::set\_audio\_encoding\_bitrate](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_set_audio_encoding_bitrate) 控制比特率。

<a id="ui" />

## UI

我们建议在向用户显示 UI 的任何地方,特别是玩家标签列表(例如记分牌),您还显示静音/说话图标作为用户的反馈。
这是通过调用 [chat\_user::chat\_indicator()](/reference/chat/gamechat2/classes/chat_user/methods/chat_user_chat_indicator) 检索表示该用户当前瞬时聊天状态的 [game\_chat\_user\_chat\_indicator](/reference/chat/gamechat2/enums/game_chat_user_chat_indicator) 枚举来完成的。以下示例演示了检索由 `chatUserA` 变量指向的 [chat\_user](/reference/chat/gamechat2/classes/chat_user/chat_user) 对象的指示器值,以确定要分配给 `iconToShow` 变量的特定图标常量值。

```cpp theme={null}
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()](/reference/chat/gamechat2/classes/chat_user/methods/chat_user_chat_indicator) 报告的值预计会频繁变化,例如,当玩家开始和停止说话时。
因此,它被设计为支持应用每个 UI 帧轮询它。

## 静音

[chat\_user::chat\_user\_local::set\_microphone\_muted()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_set_microphone_muted) 方法可用于切换本地用户麦克风的静音状态。当麦克风被静音时,不会捕获该麦克风的任何音频。如果用户在共享设备上(例如 Kinect),静音状态适用于所有用户。

[chat\_user::chat\_user\_local::microphone\_muted()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_microphone_muted) 方法可用于检索本地用户麦克风的静音状态。此方法仅反映本地用户的麦克风是否已在软件中通过调用 `chat_user::chat_user_local::set_microphone_muted()` 静音。此方法不反映由硬件控制的静音,例如通过用户耳机上的按钮。

无法通过 Game Chat 2 检索用户音频设备的硬件静音状态。

[chat\_user::chat\_user\_local::set\_remote\_user\_muted()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_set_remote_user_muted) 方法可用于切换特定本地用户对远程用户的静音状态。当远程用户被静音时,本地用户将不会听到来自远程用户的任何音频或收到任何文本消息。

## 不良信誉自动静音

通常,远程用户以未静音状态开始。
Game Chat 2 在以下情况下将用户设置为静音状态:

1. 远程用户不是本地用户的好友。
2. 远程用户具有不良信誉标志。

当用户由于此操作而被静音时,`chat_user::chat_indicator()` 返回 `game_chat_user_chat_indicator::reputation_restricted`。
此状态在第一次调用 `chat_user::chat_user_local::set_remote_user_muted()` 时被覆盖,其中远程用户作为目标用户包含在内。

## 特权和隐私

除了游戏配置的通信关系外,Game Chat 2 还强制执行特权和隐私限制。
Game Chat 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()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_get_effective_communication_relationship) 可用于帮助区分由于不完整的特权和隐私操作而导致用户无法通信的情况。
它以 [game\_chat\_communication\_relationship\_flags](/reference/chat/gamechat2/enums/game_chat_communication_relationship_flags) 的形式返回 Game Chat 2 强制执行的通信关系,并以 [game\_chat\_communication\_relationship\_adjuster](/reference/chat/gamechat2/enums/game_chat_communication_relationship_adjuster) 枚举的形式提供该关系可能不等于已配置关系的原因。

例如,如果查找操作仍在进行中,则 [game\_chat\_communication\_relationship\_adjuster](/reference/chat/gamechat2/enums/game_chat_communication_relationship_adjuster) 将为 `game_chat_communication_relationship_adjuster::initializing`。
此方法不应用于影响 UI。(有关更多信息,请参见本主题前面的 [UI](#ui) 部分。)

如果 Game Chat 2 遇到特权问题,它将在 [communication\_relationship\_adjuster\_changed](/reference/chat/gamechat2/structs/game_chat_stream_state_change) 状态更改中报告。

如果 Game Chat 2 因不可恢复的原因未能检索用户的特权,它将报告为 `game_chat_communication_relationship_adjuster::privilege_check_failure` 调整程序。

如果 Game Chat 2 因用户可能能够解决的原因未能检索用户的特权,它将报告为 `game_chat_communication_relationship_adjuster::resolve_user_issue` 调整程序。

如果用户缺少可以通过 UI 解决的特权,它将报告为 `game_chat_communication_relationship_adjuster::privilege` 调整程序。

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

以下是如何检查用户是否具有以下常见问题之一的示例。

1. 用户需要同意 XBOX 服务才能让 Game Chat 2 检查特权。
2. 用户的账户配置为拒绝特权(例如,儿童账户因此无法使用聊天)。

```cpp theme={null}
void
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::None` 和 `XUserPrivilege::Communications` 调用 [XUserResolvePrivilegeWithUiAsync](/reference/system/xuser/functions/xuserresolveprivilegewithuiasync) 以尝试解决问题。

对于以 `game_chat_communication_relationship_adjuster::resolve_user_issue` 调整程序报告的问题,您可以为 URL 传递 `nullptr` 调用 [XUserResolveIssueWithUiAsync](/reference/system/xuser/functions/xuserresolveissuewithuiasync) 以尝试解决问题。

我们建议您显示 UI 以指示存在特权问题。让用户决定是否要尝试解决问题,可以通过按下按钮或菜单选项。

用户可能无法或不愿意解决问题。
如果用户确实解决了问题,下一次将用户添加到 Game Chat 2 时将生效。

<Note>处理状态更改时(即在调用 [chat\_manager::start\_processing\_state\_changes()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_start_processing_state_changes) 之后和对应的 [chat\_manager::finish\_processing\_state\_changes()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_finish_processing_state_changes) 调用之前)不得调用 [chat\_manager::remove\_user()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_remove_user)。在处理状态更改时调用 `chat_manager::remove_user()` 可能会使与已删除用户关联的内存无效。
如果您看到 `game_chat_communication_relationship_adjuster::privilege` 调整程序并想尝试解决用户特权,应等待状态更改处理完毕后再尝试。</Note>

要从 XUID 获取 `XUserHandle`(调用 `XUserResolvePrivilegeWithUiAsync` 需要),您可以使用 [XUserFindUserById](/reference/system/xuser/functions/xuserfinduserbyid) API 获取新的 `XUserHandle`。或者,您可以保留通过 [XUserAddAsync](/reference/system/xuser/functions/xuseraddasync) 获取的那个,并跟踪哪个 XUID 映射到它。

以下是如何解决这些问题的示例。

```cpp theme={null}
// 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();
        }
    }
}
```

## 清理

当应用不再需要通过 Game Chat 2 进行通信时,您应调用 [chat\_manager::cleanup()](/reference/chat/gamechat2/classes/chat_manager/methods/chat_manager_cleanup)。
这允许 Game Chat 2 回收分配给管理通信的资源。

<a id="FAILURE_MODEL" />

## 失败模型

Game Chat 2 实现不会将异常作为非致命错误报告手段抛出。如果您愿意,可以轻松从无异常项目中使用它。
但是,Game Chat 2 会抛出异常以通知您有关致命错误的信息。

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

预期这些错误将在开发早期被捕获,并可通过修改与 Game Chat 2 交互的模式来纠正。
发生此类错误时,在引发异常之前会向调试器打印有关导致错误的原因的提示。

<a id="scenarios" />

## 如何配置常见场景

### 按住说话

按住说话应使用 [chat\_user::chat\_user\_local::set\_microphone\_muted()](/reference/chat/gamechat2/classes/chat_user/chat_user_local/methods/chat_user_local_set_microphone_muted) 实现。
调用 `set_microphone_muted(false)` 允许讲话,调用 `set_microphone_muted(true)` 限制讲话。
此方法提供 Game Chat 2 的最低延迟响应。

### 队伍

假设用户 A 和用户 B 在蓝队,用户 C 和用户 D 在红队。
每位用户在应用的唯一实例中。

在用户 A 的设备上:

```cpp theme={null}
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 的设备上:

```cpp theme={null}
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 的设备上:

```cpp theme={null}
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 的设备上:

```cpp theme={null}
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 的设备上:

```cpp theme={null}
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 的设备上:

```cpp theme={null}
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 的设备上:

```cpp theme={null}
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 的设备上:

```cpp theme={null}
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);
```

## 参考 API 文档

* [Gamechat2 (API 内容)](/reference/chat/gamechat2/gamechat2_members)
  * 结构
    * [game\_chat\_data\_frame](/reference/chat/gamechat2/structs/game_chat_data_frame)
    * [game\_chat\_state\_change](/reference/chat/gamechat2/structs/game_chat_state_change)
    * [game\_chat\_text\_chat\_received\_state\_change](/reference/chat/gamechat2/structs/game_chat_text_chat_received_state_change)
    * [game\_chat\_transcribed\_chat\_received\_state\_change](/reference/chat/gamechat2/structs/game_chat_transcribed_chat_received_state_change)
    * [communication\_relationship\_adjuster\_changed](/reference/chat/gamechat2/structs/game_chat_stream_state_change)
* [xuser (API 内容)](/reference/system/xuser/xuser_members)
  * 函数
    * [xuseraddasync](/reference/system/xuser/functions/xuseraddasync)
    * [xusergetid](/reference/system/xuser/functions/xusergetid)
    * [XUserResolveIssueWithUiAsync](/reference/system/xuser/functions/xuserresolveissuewithuiasync)
    * [XUserResolvePrivilegeWithUiAsync](/reference/system/xuser/functions/xuserresolveprivilegewithuiasync)
    * [XUserFindUserById](/reference/system/xuser/functions/xuserfinduserbyid)

## 另请参见

[Game Chat 2 简介](/services/xbox-services/multiplayer/chat/game-chat2/game-chat-2-intro)

[实时音频处理](/services/xbox-services/multiplayer/chat/game-chat2/real-time-audio-manipulation)

[API 内容 (GameChat2)](/reference/chat/gamechat2/gamechat2_members)

[Microsoft Game Development Kit](/services/playfab/sdks/platforms/gdk)


## Related topics

- [Game Chat 2](/zh-CN/services/xbox-services/multiplayer/chat/game-chat2/index.md)
- [Game Chat 2 概述](/zh-CN/services/xbox-services/multiplayer/chat/game-chat2/game-chat-2-overview.md)
- [game_chat_data_frame](/zh-CN/reference/chat/gamechat2/structs/game_chat_data_frame.md)
- [game_chat_state_change](/zh-CN/reference/chat/gamechat2/structs/game_chat_state_change.md)
- [game_chat_text_chat_received_state_change](/zh-CN/reference/chat/gamechat2/structs/game_chat_text_chat_received_state_change.md)
