> 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` current `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).

### 检查器字段

| 字段                            | 类型                                 | 默认          | 说明                                                                                                                                             |
| ----------------------------- | ---------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `_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` | —           | 步骤执行器返回时触发 `成功`                                                                                                                                |
| `_onStepFailed`               | `ConvaiActionInvocationUnityEvent` | —           | 任一步骤因任何原因失败时触发                                                                                                                                 |
| `_onStepUnhandled`            | `ConvaiActionInvocationUnityEvent` | —           | 执行器返回时触发 `未处理`                                                                                                                                 |
| `_onStepCompleted`            | `ConvaiActionStepReportUnityEvent` | —           | 在上面的结果事件之后，每个步骤只触发一次，不论结果如何                                                                                                                    |
| `_onBatchCompleted`           | `UnityEvent`                       | —           | 所有步骤完成且未被中止时触发                                                                                                                                 |
| `_onBatchAborted`             | `UnityEvent`                       | —           | 批次因失败策略而被截断时触发                                                                                                                                 |

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

### 只读运行时状态

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

`CancelOnUserSpeech` 和 `EnablePerformanceReactions` 是上面两个检查器字段对应的可读写 C# 映射——见 [抢占打断：在用户说话时取消](#barge-in-cancel-on-user-speech).

### 批次策略

批次策略控制当 Convai 在调度器仍在执行先前批次时返回一个新的动作批次时会发生什么。

| 策略               | 枚举值 | 行为                                                       |
| ---------------- | --- | -------------------------------------------------------- |
| `队列`             | `0` | 新批次会在队列中等待。当前批次会先完成，然后下一个批次才开始。适用于大多数场景。                 |
| `ReplaceCurrent` | `1` | 取消当前正在执行的步骤并清除所有已排队的批次。新批次立即开始。用于需要打断的场景（例如，“停下，改去这里。”）。 |
| `丢弃传入项`          | `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`, `未处理`, `已取消`，或 `已超时`).

| 策略          | 枚举值 | 行为                                            |
| ----------- | --- | --------------------------------------------- |
| `StopBatch` | `0` | 批次中剩余的步骤会被跳过。 `OnBatchAborted` 会触发。           |
| `继续批次`      | `1` | 不论失败与否，执行都会继续下一个步骤。 `OnBatchCompleted` 最后会触发。 |

使用 `继续批次` 当动作彼此独立时——失败的“指向”不应阻止后续的“点头或摇头”。对于 `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# 属性——请在检查器中设置。

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

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

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

信号来自角色的体现上下文事件中心（与基于服务器 VAD 的同一个 `PlayerSpeakingStateChanged` 信号，Gaze 和 Body Language 也会响应）。如果角色没有添加体现模块，抢占打断会保持空转而不是抛错——调度器只会降级，不会失败。

```csharp
使用 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 字段在检查器中订阅，或通过属性在 C# 中订阅。

#### 事件触发顺序

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

#### 在 C# 中订阅

```csharp
使用 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;
使用 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("Walk To", "Extinguisher"),
            new ConvaiActionCommand("Point At", "Extinguisher"),
            new ConvaiActionCommand("Walk To", "Exit")
        });
    }
}
```

调度器会按顺序执行这些步骤。如果 `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` 触发                                                     |
| 执行器返回 `未处理`                                                             | `OnStepUnhandled` 触发；将其视为 `StopBatch` 策略下的失败                                |
| 匹配的动作定义具有 `Enabled = false`                                             | 步骤被拒绝： `OnStepUnhandled` 触发一条消息，说明该动作已被禁用，而不运行执行器                           |
| 批次的第一步具有 `WaitForBotSpeech` 已设置（在命令或定义上）                                | `OnStepStarted` 会被延迟，直到角色开始说话、停止说话、一个回合完成，或者 `_speechGateTimeoutSeconds` 到期 |
| 玩家开始说话并带有 `_cancelOnUserSpeech` 启用                                      | 正在进行的步骤被取消，队列被清空； `OnCancelledByUserSpeech` 触发                              |
| 某个动作步骤超过其超时（其自身的 `TimeoutSeconds`，或 `_defaultStepTimeoutSeconds` 当未设置时） | 步骤结果为 `已超时`; `OnStepFailed` 触发                                              |

### 使用示例

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

**场景：** 一家企业入职模拟。每当 NPC 完成一次设备演示时，清单界面就会前进。

连接 `OnBatchCompleted` 在检查器中 `TrainingChecklistManager.AdvanceStep()`。每当 NPC 完成一次完整序列，清单就会自动前进。

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

调度器一侧无需额外代码——只需在检查器中连接 `OnBatchCompleted` UnityEvent。

#### 示例 2——导航失败时的兜底对话

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

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

```csharp
private void HandleStepFailed(ConvaiActionInvocation invocation)
{
    if (invocation.Command.Name == "Walk To")
    {
        string targetName = invocation.Command.Target ?? "that location";
        // 注入到动态上下文中，以便 NPC 自然地承认失败
        _character.DynamicContext.AddEvent($"Unable to reach {targetName} — path was blocked.");
    }
}
```

### 下一步

{% 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
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=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
