> 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 Dispatcher`                       |
| **命名空间** | `Convai.Runtime.Actions`                                         |
| **约束**   | `DisallowMultipleComponent`, `RequireComponent(ConvaiCharacter)` |

分发器必须位于同一 `GameObject` 与 `ConvaiCharacter`。每个角色只允许一个分发器。

### Inspector 字段

| 字段                          | 类型                                 | 默认值         | 描述                          |
| --------------------------- | ---------------------------------- | ----------- | --------------------------- |
| `_batchPolicy`              | `ConvaiActionBatchPolicy`          | `队列`        | 当另一个批次正在执行时，传入批次的行为         |
| `_failurePolicy`            | `ConvaiActionBatchFailurePolicy`   | `StopBatch` | 某一步失败后，是中止剩余批次还是允许其继续       |
| `_speechGateTimeoutSeconds` | `float`                            | `2`         | 新批次的第一个动作在仍会执行前，等待角色说话的最长秒数 |
| `_onBatchStarted`           | `UnityEvent`                       | —           | 在批次开始执行时触发                  |
| `_onStepStarted`            | `ConvaiActionInvocationUnityEvent` | —           | 在每一步开始时触发                   |
| `_onStepSucceeded`          | `ConvaiActionInvocationUnityEvent` | —           | 在步骤执行器返回时触发 `成功`            |
| `_onStepFailed`             | `ConvaiActionInvocationUnityEvent` | —           | 在某一步因任何原因失败时触发              |
| `_onStepUnhandled`          | `ConvaiActionInvocationUnityEvent` | —           | 当执行器返回时触发 `未处理`             |
| `_onStepCompleted`          | `ConvaiActionStepReportUnityEvent` | —           | 无论结果如何，在上述结果事件之后，每一步触发一次    |
| `_onBatchCompleted`         | `UnityEvent`                       | —           | 当所有步骤完成且未被中止时触发             |
| `_onBatchAborted`           | `UnityEvent`                       | —           | 当批次因失败策略而被提前截断时触发           |

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

### 批次策略

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

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

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

### 失败策略

失败策略控制当执行器返回非成功结果（`失败`, `未处理`, `已取消`，或 `超时`).

| 策略          | 枚举值 | 行为                                            |
| ----------- | --- | --------------------------------------------- |
| `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# 属性——请在 Inspector 中设置。

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

### 生命周期事件

分发器会在批次和步骤执行的每个关键阶段触发事件。可通过 Inspector 中的 UnityEvent 字段订阅，或通过属性在 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("移动到", "出口")
        });
    }
}
```

分发器会按顺序执行这些步骤。如果 `批处理策略` 是 `队列`，则该批次会在任何已在进行中的批次之后等待。

### 绕过分发器

如果你想在不经过分发器的目标解析和执行管线的情况下响应原始动作命令，请订阅 `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` 策略                           |
| 批次的第一步具有 `WaitForBotSpeech` 已设置（在命令或定义上） | `OnStepStarted` 将被延迟，直到角色开始说话、停止说话、回合完成，或者 `_speechGateTimeoutSeconds` 经过 |

### 使用示例

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