> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/event-system.md).

# 事件系统

Convai 事件中继组件参考，包括可用事件、载荷字段以及用于场景逻辑的订阅模式。

Convai SDK 通过一组中继组件来传达会话期间发生的事情——连接、角色发言、转录、情绪等。将其中一个 MonoBehaviour 添加到场景中的某个 GameObject 上，在 Inspector 中绑定 UnityEvent，或在代码中订阅，场景逻辑就会对 SDK 广播的内容做出响应。

***

### 两种接线方式

{% tabs %}
{% tab title="Inspector（UnityEvent）" %}

1. 通过以下方式将中继组件添加到场景中的任意 GameObject： **Add Component → Convai → Events**.
2. 在 Inspector 中分配所需引用（`ConvaiManager` 或 `ConvaiCharacter`），或启用 **Auto Resolve** ，让组件自动查找它。
3. 在 Inspector 中将处理函数连接到 UnityEvent 字段——无需代码。

适用于：连接指示器、动画触发、UI 显隐切换——任何由单个事件驱动且无需条件逻辑的场景。
{% endtab %}

{% tab title="C# 脚本" %}
通过代码订阅中继组件事件：

```csharp
public class MyHandler : MonoBehaviour
{
    [SerializeField] private ConvaiCharacterEventRelay _relay;

    private void OnEnable()
    {
        _relay.OnEmotionChanged.AddListener(HandleEmotion);
        _relay.OnSpeechStarted.AddListener(HandleSpeechStarted);
    }

    private void OnDisable()
    {
        _relay.OnEmotionChanged.RemoveListener(HandleEmotion);
        _relay.OnSpeechStarted.RemoveListener(HandleSpeechStarted);
    }

    private void HandleEmotion(CharacterEmotionRelayData data) { /* … */ }
    private void HandleSpeechStarted() { /* … */ }
}
```

适用于：条件逻辑、多事件协调、跨多个系统的数据路由。
{% endtab %}
{% endtabs %}

***

### 中继组件速查表

| 组件                           | Inspector 菜单路径                              | 适用场景                    |
| ---------------------------- | ------------------------------------------- | ----------------------- |
| `ConvaiSessionEventRelay`    | Convai/Events/Convai Session Event Relay    | 跟踪会话连接状态、处理错误、驱动连接 UI   |
| `ConvaiCharacterEventRelay`  | Convai/Events/Convai Character Event Relay  | 响应某个特定角色的发言、转录、轮次和情绪    |
| `ConvaiTranscriptEventRelay` | Convai/Events/Convai Transcript Event Relay | 全场景转录流，可按角色或是否最终稿进行可选过滤 |

***

### `ConvaiSessionEventRelay`

跟踪整个场景的会话生命周期。每个场景添加一个——它会监控由 `ConvaiManager`.

{% hint style="info" %}
如果 `ConvaiManager` 初始化晚于中继器的 `OnEnable` （例如由于脚本执行顺序），中继器会在 `LateUpdate()` 中自动重试订阅，只要它处于启用状态即可。无需手动重试逻辑。
{% endhint %}

**Inspector 字段：**

| 字段                   | 说明                                                                    |
| -------------------- | --------------------------------------------------------------------- |
| `Manager`            | 对 `ConvaiManager` 的引用。                                                |
| `AutoResolveManager` | 启用后，组件会在运行时查找 `ConvaiManager.ActiveManager` 。如果你有多个管理器，或需要显式绑定，请禁用此项。 |

**事件：**

| 事件                                | 负载                                | 触发时机                                                                                                                        |
| --------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `OnConnected`                     | —                                 | 初始连接建立（`Connecting` → `已连接`）。不会在重新连接时触发——参见 `OnReconnected`.                                                                |
| `OnDisconnected`                  | —                                 | 会话进入 `已断开连接` 状态。                                                                                                            |
| `OnReconnecting`                  | —                                 | 开始一次重新连接尝试（会话已 `已连接`，连接已断开）。                                                                                                |
| `OnReconnected`                   | —                                 | 重新连接尝试成功。会话再次处于 `已连接` 状态。                                                                                                   |
| `OnUsageLimitReached`             | —                                 | 账户的 API 使用配额已超出。                                                                                                            |
| `OnUserIdleWarning`               | `UserIdleWarningRelayData`        | 已达到用户闲置警告阈值。参见 [会话生命周期](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/session-lifecycle.md) 中触发此事件的超时策略。 |
| `OnUserIdleTimeout`               | `UserIdleTimeoutRelayData`        | 在警告后，本地计算出的空闲截止时间到达，且没有 `ExtendIdleTimeout`/`ResetIdleTimer` 调用。                                                            |
| `OnRuntimeBackgroundStateChanged` | `RuntimeBackgroundStateRelayData` | 应用进入或离开后台，或其生效的后台策略发生变化。                                                                                                    |
| `OnSessionStateChanged`           | `SessionStateChangedRelayData`    | 任何会话状态转换。每次状态变化都会触发。                                                                                                        |
| `OnSessionError`                  | `SessionErrorRelayData`           | 从会话收到错误事件。                                                                                                                  |

#### `SessionStateChangedRelayData`

| 属性                         | 类型             | 说明                                                         |
| -------------------------- | -------------- | ---------------------------------------------------------- |
| `OldState`                 | `SessionState` | 转换前的状态。                                                    |
| `NewState`                 | `SessionState` | 转换后的状态。                                                    |
| `SessionId`                | `字符串`          | 当前会话标识符。若没有活动会话则为空。                                        |
| `ErrorCode`                | `字符串`          | 如果转换是由错误引起，则为错误代码；否则为空。                                    |
| `IsError`                  | `布尔值`          | 计算结果： `NewState == Error`.                                 |
| `IsReconnecting`           | `布尔值`          | 计算结果： `OldState == Connected && NewState == Reconnecting`. |
| `IsConnectionEstablished`  | `布尔值`          | 计算结果： `OldState == Connecting && NewState == Connected`.   |
| `IsReconnectionSuccessful` | `布尔值`          | 计算结果： `OldState == Reconnecting && NewState == Connected`. |
| `IsDisconnected`           | `布尔值`          | 计算结果： `NewState == Disconnected`.                          |

#### `SessionErrorRelayData`

| 属性                  | 类型                  | 说明                           |
| ------------------- | ------------------- | ---------------------------- |
| `ErrorCode`         | `字符串`               | 机器可读的错误代码。                   |
| `消息`                | `字符串`               | 人类可读的错误描述。                   |
| `SessionId`         | `字符串`               | 错误发生时的会话标识符。                 |
| `IsRecoverable`     | `布尔值`               | SDK 是否会自动尝试恢复。               |
| `阶段`                | `SessionErrorStage` | 错误发生在连接生命周期的哪个阶段。            |
| `HttpStatusCode`    | `整数`                | 如果错误源自 API 调用，则为 HTTP 状态码。   |
| `HasHttpStatusCode` | `布尔值`               | 是否 `HttpStatusCode` 包含有意义的值。 |

`SessionErrorStage` 值： `未知`, `配置`, `ConnectApi`, `Transport`, `SessionRecovery`, `Runtime`.

#### `UserIdleWarningRelayData`

| 属性                 | 类型    | 说明                    |
| ------------------ | ----- | --------------------- |
| `RemainingSeconds` | `整数`  | 距离本地推导的闲置截止时间到达还剩多少秒。 |
| `消息`               | `字符串` | 人类可读的闲置警告消息。          |

#### `UserIdleTimeoutRelayData`

| 属性                     | 类型    | 说明                         |
| ---------------------- | ----- | -------------------------- |
| `WarningReceivedAtUtc` | `字符串` | 收到闲置警告时的 ISO 8601 时间戳。     |
| `DeadlineUtc`          | `字符串` | 本地推导的闲置截止时间的 ISO 8601 时间戳。 |

#### `RuntimeBackgroundStateRelayData`

| 属性                     | 类型                        | 说明                                                            |
| ---------------------- | ------------------------- | ------------------------------------------------------------- |
| `IsBackgrounded`       | `布尔值`                     | 应用当前是否处于后台。                                                   |
| `RequestedPolicy`      | `RuntimeBackgroundPolicy` | 项目配置的后台策略。                                                    |
| `EffectivePolicy`      | `RuntimeBackgroundPolicy` | 实际生效的后台策略。在 `RequestedPolicy` 强制回退的平台上可能不同。                   |
| `原因`                   | `RuntimePauseReason`      | 状态变更的原因——例如 `ApplicationBackground` 或 `ApplicationFocusLost`. |
| `UsedPlatformFallback` | `布尔值`                     | 计算结果： `是` 当 `RequestedPolicy != EffectivePolicy`.             |

`RuntimeBackgroundPolicy` 以及完整的暂停/恢复/重连 API 都在 [会话生命周期](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/session-lifecycle.md)中涵盖；本页只记录事件负载。

**代码示例——显示连接状态指示器：**

```csharp
public class ConnectionIndicator : MonoBehaviour
{
    [SerializeField] private ConvaiSessionEventRelay _relay;
    [SerializeField] private GameObject _connectingOverlay;

    private void OnEnable()
    {
        _relay.OnConnected.AddListener(OnConnected);
        _relay.OnDisconnected.AddListener(OnDisconnected);
        _relay.OnReconnecting.AddListener(OnReconnecting);
    }

    private void OnDisable()
    {
        _relay.OnConnected.RemoveListener(OnConnected);
        _relay.OnDisconnected.RemoveListener(OnDisconnected);
        _relay.OnReconnecting.RemoveListener(OnReconnecting);
    }

    private void OnConnected()    => _connectingOverlay.SetActive(false);
    private void OnDisconnected() => _connectingOverlay.SetActive(true);
    private void OnReconnecting() => _connectingOverlay.SetActive(true);
}
```

***

### `ConvaiCharacterEventRelay`

跟踪单个 `ConvaiCharacter`的事件。每个需要驱动场景响应的角色添加一个。

**Inspector 字段：**

| 字段                     | 说明                                            |
| ---------------------- | --------------------------------------------- |
| `角色`                   | 对 `ConvaiCharacter` 此中继器监控。                   |
| `AutoResolveCharacter` | 启用后，组件会搜索 `ConvaiCharacter` 中同一 GameObject 上的 |

**事件：**

| 事件                     | 负载                                | 触发时机                    |
| ---------------------- | --------------------------------- | ----------------------- |
| `OnTranscriptReceived` | `CharacterTranscriptRelayData`    | 每个转录片段到达时——包括临时（部分）和最终。 |
| `OnSpeechStarted`      | —                                 | 角色开始说话（音频开始播放）。         |
| `OnSpeechStopped`      | —                                 | 角色停止说话（音频结束）。           |
| `OnTurnCompleted`      | `CharacterTurnCompletedRelayData` | 角色某一轮的完整回复完成。           |
| `OnCharacterReady`     | —                                 | 角色已完全初始化并连接到会话。         |
| `OnEmotionChanged`     | `CharacterEmotionRelayData`       | 收到来自 Convai 的新情绪信号。     |

#### 多角色房间中的事件

在多角色会话处于活动状态时， `ConvaiCharacterEventRelay` 以及 `ConvaiEvents` 上的按角色事件，会通过房间成员关系而不是通过 `角色 ID` 本身来解析传入消息。名册中可能有两个具有相同 `角色 ID`的成员，而只有成员关系才能区分它们——仅按 `角色 ID` 过滤的代码无法区分这两者。有关 [处理房间事件](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/handle-roster-events.md) 上的成员作用域事件，请参见 `MultiCharacterRoomSession`.

#### `CharacterTranscriptRelayData`

| 属性              | 类型    | 说明                                  |
| --------------- | ----- | ----------------------------------- |
| `角色 ID`         | `字符串` | 角色的 ID。                             |
| `CharacterName` | `字符串` | 角色显示名称。                             |
| `文本`            | `字符串` | 转录文本。如果 `IsFinal` 为 false，则可能是部分内容。 |
| `IsFinal`       | `布尔值` | 底层转录轮次是提交、被打断还是被更正。                 |
| `TurnId`        | `字符串` | 标识此转录片段所属的轮次。                       |
| `MessageId`     | `字符串` | 此转录消息的唯一标识符。                        |
| `ResponseId`    | `字符串` | 标识此轮次所属的角色回复。                       |

#### `CharacterTurnCompletedRelayData`

| 属性               | 类型    | 说明                      |
| ---------------- | ----- | ----------------------- |
| `角色 ID`          | `字符串` | 角色的 ID。                 |
| `CharacterName`  | `字符串` | 角色显示名称。                 |
| `WasInterrupted` | `布尔值` | 如果轮次因用户打断角色而结束，则为 true。 |

#### `CharacterEmotionRelayData`

| 属性              | 类型    | 说明                                                 |
| --------------- | ----- | -------------------------------------------------- |
| `角色 ID`         | `字符串` | 角色的 ID。                                            |
| `CharacterName` | `字符串` | 角色显示名称。                                            |
| `情绪`            | `字符串` | 情绪名称（例如， `“joy”`, `“fear”`, `“sadness”`）。参见情绪功能参考。 |
| `强度`            | `整数`  | 情绪强度，按 `1`–`3` 刻度计量，其中 `1` 表示轻微， `3` 表示强烈。         |

**代码示例——在情绪变化时触发动画：**

```csharp
public class CharacterEmotionAnimator : MonoBehaviour
{
    [SerializeField] private ConvaiCharacterEventRelay _relay;
    [SerializeField] private Animator _animator;

    private static readonly int EmotionHash = Animator.StringToHash("Emotion");

    private void OnEnable() => _relay.OnEmotionChanged.AddListener(HandleEmotion);
    private void OnDisable() => _relay.OnEmotionChanged.RemoveListener(HandleEmotion);

    private void HandleEmotion(CharacterEmotionRelayData data)
    {
        _animator.SetTrigger(data.Emotion);
        _animator.SetFloat("EmotionIntensity", data.Intensity / 3f);
    }
}
```

***

### `ConvaiTranscriptEventRelay`

提供全场景转录流。不同于 `ConvaiCharacterEventRelay`，此中继器通过单个组件监控所有角色和玩家。可用它来驱动字幕 UI、会话日志或评估系统。

**Inspector 字段：**

| 字段                     | 类型              | 默认   | 说明                                                                     |
| ---------------------- | --------------- | ---- | ---------------------------------------------------------------------- |
| `Manager`              | `ConvaiManager` | —    | “ `ConvaiManager` 进行监控。                                                |
| `AutoResolveManager`   | `布尔值`           | —    | 查找 `ActiveManager` 自动完成。                                               |
| `FinalOnly`            | `布尔值`           | `否`  | 启用后，只有最终转录（已提交、被打断或被更正）才会触发事件。临时部分转录会被抑制。                              |
| `IgnoreInterimUpdates` | `布尔值`           | `是`  | 丢弃仍处于 `倾听中` 或 `传输中` 状态的轮次。稳定且已提交的轮次仍会通过。如果你的 UI 需要在角色说话时显示部分文本，请禁用此字段。 |
| `CharacterIdFilter`    | `字符串`           | `""` | 如果设置，则只有来自此 ID 角色的转录才会触发事件。留空则表示所有角色。                                  |

**事件：**

| 事件                                   | 负载                             | 触发时机                                                     |
| ------------------------------------ | ------------------------------ | -------------------------------------------------------- |
| `OnTranscriptReceived`               | `TranscriptUpdateRelayData`    | 任意角色或玩家的转录更新，采用单一统一的数据形状（受过滤条件和 `IgnoreInterimUpdates`). |
| `OnCharacterTranscriptReceived`      | `CharacterTranscriptRelayData` | 任意角色转录（受过滤条件和 `IgnoreInterimUpdates`).                   |
| `OnPlayerTranscriptReceived`         | `PlayerTranscriptRelayData`    | 任意玩家转录。                                                  |
| `OnFinalCharacterTranscriptReceived` | `CharacterTranscriptRelayData` | 仅最终角色转录，不考虑 `FinalOnly` 设置，否则角色会说出结果。                    |
| `OnFinalPlayerTranscriptReceived`    | `PlayerTranscriptRelayData`    | 仅最终玩家转录。                                                 |

#### `TranscriptUpdateRelayData`

当场景需要一个事件和一种数据形状同时处理角色与玩家转录更新，而不是分别订阅角色和玩家事件时，请使用此负载。

| 属性                    | 类型                            | 说明                                    |
| --------------------- | ----------------------------- | ------------------------------------- |
| `MessageId`           | `字符串`                         | 此转录消息的唯一标识符。                          |
| `TurnId`              | `字符串`                         | 标识此更新所属的轮次。                           |
| `ResponseId`          | `字符串`                         | 标识此轮次所属的角色回复。                         |
| `SpeakerType`         | `SpeakerType`                 | `角色` 或 `玩家`.                          |
| `PlayerOrCharacterId` | `字符串`                         | 正在说话的角色或玩家的 ID。                       |
| `DisplayName`         | `字符串`                         | 说话者的显示名称。                             |
| `ParticipantId`       | `字符串`                         | 房间参与者标识符。                             |
| `文本`                  | `字符串`                         | 转录文本。如果 `IsFinal` 为 false，则可能是部分内容。   |
| `生命周期`                | `TranscriptLifecycle`         | `传输中`, `Stable`，或 `已完成` ——本次更新文本有多稳定。 |
| `IsFinal`             | `布尔值`                         | 计算结果： `Lifecycle != Streaming`.       |
| `SourceKind`          | `TranscriptSegmentSourceKind` | 文本来源——例如 `PlayerAsr` 或 `BotOutput`.   |

#### `PlayerTranscriptRelayData`

| 属性              | 类型    | 说明                                  |
| --------------- | ----- | ----------------------------------- |
| `PlayerId`      | `字符串` | 本地玩家的标识符。                           |
| `PlayerName`    | `字符串` | 玩家显示名称。                             |
| `SpeakerId`     | `字符串` | 说话者标识符（在多参与者房间中可能与 `PlayerId` 不同）。  |
| `SpeakerName`   | `字符串` | 说话者的显示名称。                           |
| `ParticipantId` | `字符串` | 房间参与者标识符。                           |
| `TurnId`        | `字符串` | 标识此转录片段所属的轮次。                       |
| `MessageId`     | `字符串` | 此转录消息的唯一标识符。                        |
| `文本`            | `字符串` | 转录文本。如果 `IsFinal` 为 false，则可能是部分内容。 |
| `IsFinal`       | `布尔值` | 底层转录轮次是提交、被打断还是被更正。                 |

**代码示例——用于训练日志的多角色转录流：**

```csharp
public class TrainingTranscriptLog : MonoBehaviour
{
    [SerializeField] private ConvaiTranscriptEventRelay _relay;
    [SerializeField] private TMP_Text _logText;

    private readonly System.Text.StringBuilder _log = new();

    private void OnEnable()
    {
        _relay.OnFinalCharacterTranscriptReceived.AddListener(OnCharacterLine);
        _relay.OnFinalPlayerTranscriptReceived.AddListener(OnPlayerLine);
    }

    private void OnDisable()
    {
        _relay.OnFinalCharacterTranscriptReceived.RemoveListener(OnCharacterLine);
        _relay.OnFinalPlayerTranscriptReceived.RemoveListener(OnPlayerLine);
    }

    private void OnCharacterLine(CharacterTranscriptRelayData data)
    {
        _log.AppendLine($"[{data.CharacterName}]: {data.Text}");
        _logText.text = _log.ToString();
    }

    private void OnPlayerLine(PlayerTranscriptRelayData data)
    {
        _log.AppendLine($"[学员]: {data.Text}");
        _logText.text = _log.ToString();
    }
}
```

***

### 订阅生命周期

Relay MonoBehaviour 组件会自动管理自己的订阅。它们在 `OnEnable` 运行时订阅，并在 `OnDisable` 运行之前注册。

反订阅。

```csharp
通过 C# 订阅时，请遵循相同模式：
private void OnEnable()  => _relay.OnConnected.AddListener(MyHandler);
```

{% hint style="warning" %}
不要在 `Start()` 中订阅而不在 `OnDestroy()`中进行匹配的反订阅。中继组件可以被禁用并重新启用；来自 `Start()` 的订阅如果没有清理，在中继器禁用后会导致重复处理器或空引用错误。
{% endhint %}

***

### 对话目标和可用性事件

这些事件没有中继组件。请通过 `ConvaiManager.ActiveManager.Events` 上的类型化 hub 订阅，而不是向场景中添加组件。

**事件：**

| 事件                                  | 负载                                | 触发时机                                                      |
| ----------------------------------- | --------------------------------- | --------------------------------------------------------- |
| `OnConversationTargetChanged`       | `ConversationTargetChanged`       | 对话在多角色房间中从一个角色转移到另一个角色——在请求转移时触发一次，在服务确认时触发一次，若被拒绝则再触发一次。 |
| `OnConversationAvailabilityChanged` | `ConversationAvailabilityChanged` | “玩家现在能否说话”的答案会针对被对话对象的角色发生变化。                             |
| `OnRoomRosterChanged`               | `RoomRosterChanged`               | 运行期间编辑已连接房间的名册——角色加入、离开，或编辑被拒绝。                           |

**代码示例——响应目标切换和名册变化：**

```csharp
using Convai.Domain.DomainEvents.Runtime;
using Convai.Runtime.Facades;
using UnityEngine;

public class ConversationTargetingMonitor : MonoBehaviour
{
    private void OnEnable()
    {
        var manager = ConvaiManager.ActiveManager;
        if (manager == null) return;

        manager.Events.OnConversationTargetChanged += HandleTargetChanged;
        manager.Events.OnRoomRosterChanged += HandleRosterChanged;
    }

    private void OnDisable()
    {
        var manager = ConvaiManager.ActiveManager;
        if (manager == null) return;

        manager.Events.OnConversationTargetChanged -= HandleTargetChanged;
        manager.Events.OnRoomRosterChanged -= HandleRosterChanged;
    }

    private void HandleTargetChanged(ConversationTargetChanged e)
    {
        if (e.Phase == ConversationTargetChangePhase.Confirmed)
            Debug.Log($"现在正在与 {e.CharacterName} 对话。");
    }

    private void HandleRosterChanged(RoomRosterChanged e)
    {
        if (e.Change == RoomRosterChange.Joined)
            Debug.Log($"{e.CharacterName} 加入了房间。");
    }
}
```

#### `ConversationTargetChanged`

| 属性              | 类型                              | 说明                                              |
| --------------- | ------------------------------- | ----------------------------------------------- |
| `Phase`         | `ConversationTargetChangePhase` | 切换进展到什么程度： `Requested`, `Confirmed`，或 `Failed`. |
| `角色 ID`         | `字符串`                           | 对话正切换到的角色的 Convai Character ID。                 |
| `CharacterName` | `字符串`                           | 该角色的显示名称。                                       |
| `成员 ID`         | `字符串`                           | 该切换所针对的房间成员关系。                                  |
| `原因`            | `字符串`                           | 切换被拒绝的原因。除非 `Phase` 是 `Failed`.                 |
| `时间戳`           | `DateTime`                      | 到达该阶段时的 UTC 时间。                                 |

`ConversationTargetChangePhase` 值： `Requested` （管理器已获取路由窗口，并即将发送切换）， `Confirmed` （服务响应已协调出权威路由）， `Failed` （请求的切换被拒绝）。

#### `ConversationAvailabilityChanged`

| 属性                     | 类型                               | 说明                                 |
| ---------------------- | -------------------------------- | ---------------------------------- |
| `可用性`                  | `ConvaiConversationAvailability` | 当前裁定。                              |
| `PreviousAvailability` | `ConvaiConversationAvailability` | 它所替代的裁定。                           |
| `角色 ID`                | `字符串`                            | 被提及角色的 Convai Character ID（如果有的话）。 |
| `CharacterName`        | `字符串`                            | 该角色的显示名称。                          |
| `时间戳`                  | `DateTime`                       | 判定结果变更时的 UTC 时间。                   |
| `CanAcceptPlayerInput` | `布尔值`                            | 玩家当前是否可以发送语音或文本。                   |

`ConvaiConversationAvailability` 值，按生命周期顺序： `NoCharacter`, `Offline`, `Connecting`, `Preparing`, `就绪`, `Answering`, `Unavailable`。仅 `就绪` 和 `Answering` 接受玩家输入。

#### `RoomRosterChanged`

| 属性              | 类型                 | 说明                           |
| --------------- | ------------------ | ---------------------------- |
| `变更`            | `RoomRosterChange` | 发生了什么： `加入`, `离开`，或 `拒绝`.    |
| `成员 ID`         | `字符串`              | 受影响的房间成员关系。对于从未获得成员关系的加入则为空。 |
| `角色 ID`         | `字符串`              | 此事件所指的 Convai Character ID。  |
| `CharacterName` | `字符串`              | 该角色的显示名称。                    |
| `RosterSize`    | `整数`               | 此变更后房间包含多少角色。                |
| `原因`            | `字符串`              | 编辑被拒绝的原因。除非 `变更` 是 `拒绝`.     |
| `时间戳`           | `DateTime`         | 名册移动时的 UTC 时间。               |

#### `LocalPlayerActivityChanged`

SDK 对玩家是否开始说话的本地判断——只是一个提示，不是服务自身的裁决。它在 `ConvaiEvents`上没有属性；请改为通过原始 hub 订阅：

```csharp
using Convai.Domain.DomainEvents.Runtime;
using Convai.Domain.EventSystem;
using UnityEngine;

public class LocalPlayerActivityMonitor : MonoBehaviour
{
    private SubscriptionToken _token;

    private void OnEnable()
    {
        var hub = ConvaiManager.ActiveManager?.Events?.Raw;
        if (hub == null) return;
        _token = hub.Subscribe<LocalPlayerActivityChanged>(HandleActivity);
    }

    private void OnDisable() => ConvaiManager.ActiveManager?.Events?.Raw?.Unsubscribe(_token);

    private void HandleActivity(LocalPlayerActivityChanged e) =>
        Debug.Log(e.IsActive ? "玩家开始说话。" : "玩家停止说话。");
}
```

| 属性         | 类型                          | 说明                                       |
| ---------- | --------------------------- | ---------------------------------------- |
| `IsActive` | `布尔值`                       | 是否 `来源` 当前能看到玩家。                         |
| `来源`       | `LocalPlayerActivitySource` | 促发此事件的本地证据： `Microphone` 或 `PushToTalk`. |
| `级别`       | `float`                     | 麦克风相对于其测得噪声底有多响。对于 `PushToTalk` 和下降沿时为零。 |
| `时间戳`      | `DateTime`                  | 本地证据变化时的 UTC 时间。                         |

请仅将其视为“有人正在开始和我说话”的提示——绝不要据此路由消息、提交轮次或进行计费。服务自己的裁决是 `PlayerSpeakingStateChanged`.

***

### `ConvaiNotificationEventBridge`

`ConvaiNotificationEventBridge` 不是中继组件。它是一个内部服务，将会话错误领域事件桥接到通知 UI 系统，并带有冷却去重机制，以防同一错误通知反复出现。

| 属性                | 类型      | 默认   | 说明              |
| ----------------- | ------- | ---- | --------------- |
| `CooldownSeconds` | `float` | `10` | 显示同一类通知之间的最短秒数。 |

大多数项目不会直接与此类交互。它由 SDK 启动程序实例化并管理。如果你正在使用 `IConvaiNotificationService`构建自定义通知系统，你可以使用 `ConvaiNotificationEventBridge` 将会话错误事件集成到你的系统中。不同于上面的中继组件， `ConvaiNotificationEventBridge` 不是通过 **添加组件** 添加到场景中的——它是在 SDK 启动期间以程序方式实例化的。

***

### 使用示例

#### 示例 1：训练模拟——连接遮罩层

在会话尚未建立时显示“正在连接…”遮罩层。

```csharp
[SerializeField] private ConvaiSessionEventRelay _sessionRelay;
[SerializeField] private CanvasGroup _loadingOverlay;

private void OnEnable()
{
    _sessionRelay.OnConnected.AddListener(OnConnected);
    _sessionRelay.OnDisconnected.AddListener(OnDisconnected);
    _sessionRelay.OnReconnecting.AddListener(OnReconnecting);
}

private void OnDisable()
{
    _sessionRelay.OnConnected.RemoveListener(OnConnected);
    _sessionRelay.OnDisconnected.RemoveListener(OnDisconnected);
    _sessionRelay.OnReconnecting.RemoveListener(OnReconnecting);
}

private void OnConnected()    => _loadingOverlay.alpha = 0f;
private void OnDisconnected() => _loadingOverlay.alpha = 1f;
private void OnReconnecting() => _loadingOverlay.alpha = 0.5f;
```

**预期结果：** 当会话未连接时，遮罩层淡入；当连接建立时，遮罩层淡出。

***

#### 示例 2：医疗训练师——情绪触发的角色响应

患者角色的面部表情和姿态会根据 Convai 检测到的情绪而变化。

```csharp
[SerializeField] private ConvaiCharacterEventRelay _patientRelay;
[SerializeField] private PatientExpressionController _expressionController;

private void OnEnable() => _patientRelay.OnEmotionChanged.AddListener(ApplyEmotion);
private void OnDisable() => _patientRelay.OnEmotionChanged.RemoveListener(ApplyEmotion);

private void ApplyEmotion(CharacterEmotionRelayData data)
{
    _expressionController.SetExpression(data.Emotion, data.Intensity / 3f);
}
```

**预期结果：** 当来自 Convai 的情绪信号到达时，患者角色的视觉表情会实时更新。

***

#### 示例 3：过滤到单个角色的共享转录流

一个企业入职模拟包含多个 NPC 角色，但字幕面板中只显示主讲师的台词。

在 `ConvaiTranscriptEventRelay` 组件的 Inspector 中：

* 设置 `CharacterIdFilter` 设置为讲师角色的 ID（例如， `"abc123"`).
* 启用 `FinalOnly` 以仅显示已提交的转录行。
* 将 `OnFinalCharacterTranscriptReceived` 到你的字幕 UI。

**预期结果：** 字幕面板中只会显示讲师已完成的句子。场景中的其他角色不会影响 UI。

***

### 故障排查

| 症状                                       | 可能原因                                          | 修复方法                                                                                                      |
| ---------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Relay 在场景开始后没有触发任何事件                     | `ConvaiManager` 在 relay 的 `OnEnable` 运行之前未初始化 | 无需操作——relay 会在 `LateUpdate()` 启用时重试。请验证 `ConvaiManager` 已存在并在场景中处于活动状态。                                   |
| `ConvaiCharacterEventRelay` 不触发任何事件      | `ConvaiCharacter` 未在分配的 GameObject 上找到        | 验证 `ConvaiCharacter` 位于 **同一个** GameObject 上，或者显式分配该引用。 `AutoResolveCharacter` 只搜索同一个 GameObject——不包括父对象。 |
| 中间转录更新未到达                                | `IgnoreInterimUpdates` 是 `是` 默认情况下            | 设置 `IgnoreInterimUpdates = false` 时 `ConvaiTranscriptEventRelay` 以接收部分转录更新。                               |
| 事件处理器对单个事件触发多次                           | 处理器订阅于 `Start()` 且未清理；relay 曾被禁用并重新启用         | 将订阅移到 `OnEnable()` 并在 `OnDisable()`.                                                                      |
| `OnCharacterTranscriptReceived` 未对预期角色触发 | `CharacterIdFilter` 被设置为不同的角色 ID              | 清除 `CharacterIdFilter` ，或将其设置为正确的角色 ID。                                                                   |

***

### 下一步

你现在已经获得了所有 relay 组件、事件载荷和订阅模式的完整参考。请继续查看 Features 部分，探索各项 SDK 功能。

{% content-ref url="/pages/8c561f7c198c46628ed5818040fdaa9af3397caf" %}
[功能](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/event-system.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
