> 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/features/character-actions/dispatcher-and-batch-policies.md).

# 分发器和批处理策略

`ConvaiActionDispatcher` 是动作系统的运行时执行层。它会监听来自 Convai 的命令批次，依据当前会话的配置解析每个动作和目标，并逐步调用已绑定的执行器组件。两种策略控制在执行期间收到新批次以及某一步失败时会发生什么。语音门控还可以将新批次的第一个动作保持到角色开始说话为止，而分发器也可以在玩家开始说话时可选地取消自身工作。

### 组件概览

| 属性       | 值                                                                |
| -------- | ---------------------------------------------------------------- |
| **菜单路径** | `添加组件 → Convai → Convai Action Runner`                           |
| **命名空间** | `Convai.Runtime.Actions`                                         |
| **约束**   | `DisallowMultipleComponent`, `RequireComponent(ConvaiCharacter)` |

分发器必须与 `GameObject` 处于同一 `ConvaiCharacter`。每个角色只允许一个分发器。在 Actions Editor 的 **角色设置** 选项卡中，此组件标记为 **Convai 动作运行器** 在 **动作由** ——参见 [配置角色动作](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/character-actions/configuring-actions.md#where-action-behaviors-live).

### Inspector 字段

| 字段                            | 类型                                 | 默认值         | 描述                                                                                                                                               |
| ----------------------------- | ---------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `_batchPolicy`                | `ConvaiActionBatchPolicy`          | `队列`        | 当另一个批次正在执行时，传入批次的行为方式                                                                                                                            |
| `_failurePolicy`              | `ConvaiActionBatchFailurePolicy`   | `StopBatch` | 某一步失败是否中止剩余批次或允许其继续                                                                                                                              |
| `_speechGateTimeoutSeconds`   | `float`                            | `2`         | 新批次的第一个动作在最终仍继续执行前，等待角色说话的最长秒数                                                                                                                   |
| `_defaultStepTimeoutSeconds`  | `float`                            | `60`        | 任何动作在被报告为超时前可运行的最长时间，仅在该动作自身的 `TimeoutSeconds` 为 `0`。动作自身的超时在编写时始终优先。 `0` 会完全移除此安全网。                                                             |
| `_cancelOnUserSpeech`         | `bool`                             | `false`     | 启用后，玩家一开始说话就会取消正在进行的批次并清空队列——效果与 `ReplaceCurrent`相同，只是由玩家触发，而不是由新的后端批次触发                                                                         |
| `_enablePerformanceReactions` | `bool`                             | `true`      | 启用后，会通知任何 `IActionPerformanceReactor` 注册在角色具身上下文中的对等组件（Gaze 的“看向你行动的位置”、Body Language 的认可点头、Emotion 的结果情绪节拍）有关批次和步骤生命周期的通知。若没有具身上下文或反应器，则不执行任何操作 |
| `_onBatchStarted`             | `UnityEvent`                       | —           | 批次开始执行时触发                                                                                                                                        |
| `_onStepStarted`              | `ConvaiActionInvocationUnityEvent` | —           | 每一步开始时触发                                                                                                                                         |
| `_onStepSucceeded`            | `ConvaiActionInvocationUnityEvent` | —           | 当步骤执行器返回时触发 `Succeeded`                                                                                                                          |
| `_onStepFailed`               | `ConvaiActionInvocationUnityEvent` | —           | 某一步因任何原因失败时触发                                                                                                                                    |
| `_onStepUnhandled`            | `ConvaiActionInvocationUnityEvent` | —           | 当执行器返回 `Unhandled`                                                                                                                               |
| `_onStepCompleted`            | `ConvaiActionStepReportUnityEvent` | —           | 无论结果如何，在上述结果事件之后，每一步都会触发一次                                                                                                                       |
| `_onBatchCompleted`           | `UnityEvent`                       | —           | 当所有步骤在未被中止的情况下完成时触发                                                                                                                              |
| `_onBatchAborted`             | `UnityEvent`                       | —           | 当批次因失败策略而被提前中止时触发                                                                                                                                |

`ConvaiActionInvocationUnityEvent` 是一个可序列化的 `UnityEvent<ConvaiActionInvocation>`。在 Inspector 中像标准的 `UnityEvent` ——事件参数携带完整的调用上下文（动作名称、目标、角色、批次和步骤索引）。 `ConvaiActionStepReportUnityEvent` 是一个可序列化的 `UnityEvent<ConvaiActionStepReport>`，通过公开的 `OnStepCompleted` 属性暴露——当你希望为每个步骤结果只使用一个订阅点，而不是分别连接时，就使用它 `OnStepSucceeded`/`OnStepFailed`/`OnStepUnhandled` 。

### 只读运行时状态

| 属性                  | 类型                               | 描述                              |
| ------------------- | -------------------------------- | ------------------------------- |
| `IsBusy`            | `bool`                           | 分发器当前是否正在运行批次。                  |
| `PendingBatchCount` | `int`                            | 有多少已接收的批次正在当前运行批次之后等待。          |
| `CurrentActionName` | `string`                         | 当前正在运行的动作显示名称，或在步骤之间为空。         |
| `BatchPolicy`       | `ConvaiActionBatchPolicy`        | 编写的批次策略（代码中只读；在 Inspector 中设置）。 |
| `FailurePolicy`     | `ConvaiActionBatchFailurePolicy` | 编写的失败策略（代码中只读；在 Inspector 中设置）。 |

`CancelOnUserSpeech` 和 `EnablePerformanceReactions` 是上述两个 Inspector 字段的 C# 可读写镜像——参见 [抢话打断：在用户说话时取消](#barge-in-cancel-on-user-speech).

### 批次策略

当 Convai 返回新的动作批次，而分发器仍在执行前一个批次时，批次策略决定会发生什么。

| 策略               | 枚举值 | 行为                                                      |
| ---------------- | --- | ------------------------------------------------------- |
| `队列`             | `0` | 新批次在队列中等待。当前批次完成后，下一个才开始。适用于大多数场景。                      |
| `ReplaceCurrent` | `1` | 取消当前正在执行的步骤并清除所有排队批次。新批次立即开始。适用于中断驱动的场景（例如，“停下，改来这里”）。  |
| `DropIncoming`   | `2` | 在当前批次和所有排队批次完成之前，丢弃新批次。适用于进行中的序列不能被中断的情况（例如，必须完成的安全演示）。 |

{% hint style="info" %}
`ReplaceCurrent` 取消 **当前正在运行的执行器步骤** 通过 `CancellationToken` 并在开始新批次前清除所有待处理批次。要实现即时取消，执行器必须遵守取消令牌——参见 [编写自定义动作执行器](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/character-actions/writing-custom-executors.md).
{% endhint %}

### 失败策略

当执行器返回非成功结果（`Failed`, `Unhandled`, `已取消`，或 `TimedOut`).

| 策略              | 枚举值 | 行为                                             |
| --------------- | --- | ---------------------------------------------- |
| `StopBatch`     | `0` | 批次中剩余的步骤将被跳过。 `OnBatchAborted` 会被触发。           |
| `ContinueBatch` | `1` | 无论失败如何，执行都会继续进入下一步。 `OnBatchCompleted` 会在最后触发。 |

使用 `ContinueBatch` 当动作彼此独立时——失败的“指向”不应阻止后续的“点头或摇头”。使用 `StopBatch` （默认值）用于相互依赖的序列——失败的“走到”应阻止后续期望角色已到达的动作。

### 在角色说话时对第一个动作进行门控

Convai 可以为某个动作加标记，使分发器将其延迟到角色开始说话为止。有两个字段控制这一点——一个由 Convai 在命令中设置，另一个可在匹配的动作定义上本地编写。

| 字段                           | 位置                       | 类型      | 默认值     | 描述                                                         |
| ---------------------------- | ------------------------ | ------- | ------- | ---------------------------------------------------------- |
| `WaitForBotSpeech`           | `ConvaiActionCommand`    | `bool`  | `false` | 由 Convai 在后端命令中设置。 `true` 对批次的第一步进行门控。                     |
| `DelayAfterBotSpeechSeconds` | `ConvaiActionCommand`    | `float` | `0`     | 门控释放后的额外延迟。仅 `WaitForBotSpeech` 为 `true`.                  |
| `WaitForBotSpeech`           | `ConvaiActionDefinition` | `bool`  | `false` | 在动作定义上编写的本地覆盖。并且在以下情况下也会对第一步进行门控： `true`.                  |
| `DelayAfterBotSpeechSeconds` | `ConvaiActionDefinition` | `float` | `0`     | 门控释放后的额外延迟。仅 `WaitForBotSpeech` 为 `false` 且定义中的其值为 `true`. |

分发器只会在批次的第一步检查这些字段（`stepIndex == 0`）；同一批次中的后续步骤不会等待。只要命令的 `WaitForBotSpeech` 或匹配定义的 `WaitForBotSpeech` 为 `true`。当两者都不是 `true`时，该步骤会立即运行，不进行门控。

### 语音门控超时

`_speechGateTimeoutSeconds` 限制被门控的第一步等待的最长时间，单位为秒。默认值为 `2`。此字段没有公开的 C# 属性——请在 Inspector 中设置。

在门控打开期间，分发器会监听 `ConvaiCharacter.OnSpeechStarted`, `ConvaiCharacter.OnSpeechStopped`，以及 `ConvaiCharacter.OnTurnCompleted`。门控会在这些事件中最先触发的那个发生时释放，或者在 `_speechGateTimeoutSeconds` 经过该时间后释放，以先发生者为准。 `OnStepStarted` 仅在门控释放后触发。

### 抢话打断：在用户说话时取消

启用 `_cancelOnUserSpeech` 使分发器在玩家一开始说话时就取消正在进行的批次并清空队列——效果与 `ReplaceCurrent` 批次策略相同，只是由玩家触发，而不是由新的后端批次触发。默认关闭，因此在你选择启用之前，现有场景不会改变。

该信号来自角色的具身上下文事件中心（与基于服务器 VAD 的 `PlayerSpeakingStateChanged` Gaze 和 Body Language 已经会响应的信号）。如果角色没有添加具身模块，抢话打断会保持无效而不是抛出异常——分发器只是降级，不会失败。

```csharp
using Convai.Runtime.Actions;
using UnityEngine;

public sealed class BargeInFeedback : MonoBehaviour
{
    [SerializeField] private ConvaiActionDispatcher _dispatcher;

    private void OnEnable() => _dispatcher.OnCancelledByUserSpeech += HandleCancelled;
    private void OnDisable() => _dispatcher.OnCancelledByUserSpeech -= HandleCancelled;

    private void HandleCancelled(string interruptedAction) =>
        Debug.Log($"抢话打断已取消：{interruptedAction}");
}
```

`OnCancelledByUserSpeech` 携带被中断动作的显示名称；如果还没有任何动作开始执行，则为空字符串。

### 生命周期事件

分发器会在批次和步骤执行的每个关键阶段触发事件。可通过 UnityEvent 字段在 Inspector 中订阅，或通过属性在 C# 中订阅。

#### 事件触发顺序

```
OnBatchStarted
  → OnStepStarted       （每一步）
  → OnStepSucceeded     （如果执行器返回 Succeeded）
     或
  → OnStepFailed        （如果执行器返回 Failed、Canceled 或 TimedOut）
     或
  → OnStepUnhandled     （如果执行器返回 Unhandled）
  → OnStepCompleted     （总是在上述结果事件之后触发，每一步都会触发）
OnBatchCompleted  （所有步骤完成，或 ContinueBatch 允许失败继续通过）
  或
OnBatchAborted    （StopBatch 策略在失败后提前中止批次）
```

#### 在 C# 中订阅

```csharp
using Convai.Runtime.Actions;
using UnityEngine;

public sealed class ActionFeedback : MonoBehaviour
{
    [SerializeField] private ConvaiActionDispatcher _dispatcher;

    private void OnEnable()
    {
        _dispatcher.OnBatchStarted.AddListener(HandleBatchStarted);
        _dispatcher.OnStepSucceeded.AddListener(HandleStepSucceeded);
        _dispatcher.OnStepFailed.AddListener(HandleStepFailed);
        _dispatcher.OnBatchCompleted.AddListener(HandleBatchCompleted);
        _dispatcher.OnBatchAborted.AddListener(HandleBatchAborted);
    }

    private void OnDisable()
    {
        _dispatcher.OnBatchStarted.RemoveListener(HandleBatchStarted);
        _dispatcher.OnStepSucceeded.RemoveListener(HandleStepSucceeded);
        _dispatcher.OnStepFailed.RemoveListener(HandleStepFailed);
        _dispatcher.OnBatchCompleted.RemoveListener(HandleBatchCompleted);
        _dispatcher.OnBatchAborted.RemoveListener(HandleBatchAborted);
    }

    private void HandleBatchStarted() => Debug.Log("批次已开始");
    private void HandleStepSucceeded(ConvaiActionInvocation inv) =>
        Debug.Log($"步骤成功：{inv.Command.Name}");
    private void HandleStepFailed(ConvaiActionInvocation inv) =>
        Debug.LogWarning($"步骤失败：{inv.Command.Name}");
    private void HandleBatchCompleted() => Debug.Log("批次已完成");
    private void HandleBatchAborted() => Debug.LogWarning("批次已中止");
}
```

### 手动注入批次

`EnqueueActions(IReadOnlyList<ConvaiActionCommand> actions)` 以编程方式将一个批次提交给分发器，并遵循当前的批次和失败策略。用于脚本化演示序列、自动化测试运行，或由游戏事件而非玩家说话触发的 NPC 行为。

```csharp
using System.Collections.Generic;
using Convai.Runtime.Actions;
using Convai.Shared.Types;
using UnityEngine;

public sealed class DemoTrigger : MonoBehaviour
{
    [SerializeField] private ConvaiActionDispatcher _dispatcher;

    public void RunSafetyDemo()
    {
        _dispatcher.EnqueueActions(new List<ConvaiActionCommand>
        {
            new ConvaiActionCommand("走到", "灭火器"),
            new ConvaiActionCommand("指向", "灭火器"),
            new ConvaiActionCommand("走到", "出口")
        });
    }
}
```

分发器会按顺序执行这些步骤。如果 `BatchPolicy` 为 `队列`，此批次会等待在任何已在进行的批次之后。现在，手动注入的批次会像后端批次一样被处理——线路文本清理、参数解析和目标解析都会执行——因此无法解析的动作名称或目标会产生相同的 `动作`类别的控制台说明，就像真实对话一样，而不是只在稍后作为步骤失败才浮现。此路径不会像后端路径那样拒绝命令；步骤自身的前置条件（定义、执行器、目标要求）仍然是门槛。

### 绕过分发器

如果你想在不经过分发器的目标解析和执行流水线的情况下响应原始动作命令，请订阅 `ConvaiCharacter.OnActionsReceived` 直接订阅任一投影，或两者都订阅：

```csharp
using System.Collections.Generic;
using Convai.Runtime.Components;
using Convai.Shared.Types;
using UnityEngine;

public sealed class ManualActionHandler : MonoBehaviour
{
    [SerializeField] private ConvaiCharacter _character;

    private void OnEnable() =>
        _character.OnActionsReceived += HandleActions;

    private void OnDisable() =>
        _character.OnActionsReceived -= HandleActions;

    private void HandleActions(IReadOnlyList<ConvaiActionCommand> commands)
    {
        foreach (ConvaiActionCommand cmd in commands)
            Debug.Log($"动作：{cmd.Name}，目标：{cmd.Target}");
    }
}
```

{% hint style="warning" %}
绕过分发器意味着没有自动目标解析、没有批次/失败策略，也没有生命周期事件。这适用于只读观察或自定义分发流水线，但不适用于应由 SDK 驱动行为的典型游戏玩法。
{% endhint %}

### 分发器生命周期行为

| 情况                                                                       | 分发器行为                                                                    |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| 分发器已禁用                                                                   | 当前工作被取消；队列被清空                                                            |
| 分发器已销毁                                                                   | 与禁用时相同                                                                   |
| 收到空批次                                                                    | 静默忽略——不会触发任何事件                                                           |
| 动作名称不在本地定义中                                                              | 步骤失败： `OnStepFailed` 触发； `StopBatch` 中止批次                                |
| 未分配执行器字段                                                                 | 步骤失败： `OnStepFailed` 触发                                                  |
| 未满足目标要求                                                                  | 步骤失败： `OnStepFailed` 触发                                                  |
| 执行器返回 `Unhandled`                                                        | `OnStepUnhandled` 触发；在以下情况下视为失败： `StopBatch` 策略                          |
| 匹配的动作定义具有 `Enabled = false`                                              | 步骤被拒绝： `OnStepUnhandled` 触发时会附带一条将该动作命名为已禁用的消息，而不会运行执行器                  |
| 批次的第一步具有 `WaitForBotSpeech` 已设置（在命令或定义上）                                 | `OnStepStarted` 会延迟，直到角色开始说话、停止说话、回合完成，或者 `_speechGateTimeoutSeconds` 经过 |
| 玩家开始说话且 `_cancelOnUserSpeech` 已启用                                        | 正在进行的步骤被取消，队列被清空； `OnCancelledByUserSpeech` 触发                           |
| 某个动作步骤超过其超时时间（其自身的 `TimeoutSeconds`，或 `_defaultStepTimeoutSeconds` 未设置时） | 步骤结果为 `TimedOut`; `OnStepFailed` 触发                                      |

### 使用示例

#### 示例 1 — 培训清单集成

**场景：** 一个企业入职模拟。每当 NPC 完成一次设备演示，检查清单 UI 就会前进。

将……绑定 `OnBatchCompleted` 在 Inspector 中将其设置为 `TrainingChecklistManager.AdvanceStep()`。每当 NPC 完成一个完整序列，清单就会自动前进。

```csharp
// TrainingChecklistManager.cs
public void AdvanceStep()
{
    _currentStep++;
    UpdateChecklistUI();
}
```

分发器一侧无需额外代码——只需连接 `OnBatchCompleted` Inspector 中的 UnityEvent。

#### 示例 2 — 导航失败时的后备对话

**场景：** 当 NPC 无法到达目标（NavMesh 路径被阻塞）时，它应说出一条后备台词，而不是静默停止。

订阅到 `OnStepFailed` 并注入一个动态上下文更新：

```csharp
private void HandleStepFailed(ConvaiActionInvocation invocation)
{
    if (invocation.Command.Name == "走到")
    {
        string targetName = invocation.Command.Target ?? "那个位置";
        // 注入到动态上下文中，使 NPC 自然地承认失败
        _character.DynamicContext.AddEvent($"无法到达 {targetName} —— 路径被阻塞。");
    }
}
```

### 下一步

{% content-ref url="/pages/c5598abd0ddd72fa75a0989ad602a5b4d12e16d7" %}
[编写自定义动作执行器](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/character-actions/writing-custom-executors.md)
{% endcontent-ref %}

{% content-ref url="/pages/0341126fa4c492311dab4fb6aca6d0c64191016b" %}
[角色动作脚本参考](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/character-actions/actions-scripting-reference.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/features/character-actions/dispatcher-and-batch-policies.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.
