> 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/unity-plugin-beta-overview/features/actions/dispatcher-and-batch-policies.md).

# 分发器与批处理策略

## ConvaiActionDispatcher 组件

`ConvaiActionDispatcher` 是动作系统的运行时引擎。它挂在你的 NPC 的 GameObject 上，监听来自 Convai 后端的动作命令，并按照你配置的策略，依次为每个命令运行正确的执行器。

通过选择你的 NPC 的 GameObject 并单击 **添加组件 → Convai Action Dispatcher**.

{% hint style="info" %}
`ConvaiActionDispatcher` 需要一个 `ConvaiCharacter` 组件，且位于同一个 GameObject 上。它会订阅 `ConvaiCharacter.OnActionsReceived` 并在启用时自动订阅。
{% endhint %}

<figure><img src="/files/e55d8b16b7de1d6a975f1d00b2eb83e35fdface2" alt=""><figcaption></figcaption></figure>

***

## 理解批次

Convai 后端可能返回 **在单个响应中返回多个动作**。这组动作称为一个 **批次**.

例如，如果玩家说：

> *“把安全头盔捡起来并拿给我。”*

后端可能返回一个包含两个动作的批次：

1. `Move To` → 目标： `安全头盔`
2. `拾取` → 目标： `安全头盔`

调度器会在批次内运行动作 **按顺序** ——一次一个，按顺序执行。它会等每个执行器完成后再开始下一步。

```mermaid
flowchart LR
    A([已接收批次\n2 个动作]) --> B[步骤 1：移动到\n安全头盔]
    B -->|Succeeded| C[步骤 2：拾取\n安全头盔]
    C -->|Succeeded| D([批次完成])
    B -->|Failed| E([批次已中止\n或继续])
```

***

## 批次策略

该 **批次策略** 决定当调度器仍在执行上一批动作时，新批次动作到达会发生什么。

在以下位置配置它： **调度** 部分中的 `ConvaiActionDispatcher` 检视面板。

| 策略               | 行为                     | 最适合                        |
| ---------------- | ---------------------- | -------------------------- |
| **Queue** *（默认）* | 新批次会在队列中等待。各批次按接收顺序执行。 | 回合制体验、训练模拟、顺序叙事            |
| **替换当前**         | 正在运行的批次会立即取消。新批次会立刻开始。 | 适合实时游戏，玩家应能在动作执行中途重新引导 NPC |
| **丢弃传入**         | 新批次会被丢弃。当前批次会不受干扰地完成。  | 过场动画、脚本化序列、不可中断行为          |

{% hint style="info" %}
**Queue** 对大多数应用来说，这是最安全的默认选项。NPC 会在开始下一个任务之前完成当前任务，从而产生可预测的行为。
{% endhint %}

{% hint style="info" %}
**替换当前** 让 NPC 在实时场景中感觉更灵敏、更有响应。适用于玩家指令应始终立即生效的情况。
{% endhint %}

<figure><img src="/files/3689d64b2efb727ea09c0efd6e211c001db2062b" alt=""><figcaption></figcaption></figure>

***

## 失败策略

该 **失败策略** 决定当批次中的某个动作失败时会发生什么——例如，执行器找不到目标，或者 NavMesh 路径无效。

| 策略              | 行为                                                    |
| --------------- | ----------------------------------------------------- |
| **停止批次** *（默认）* | 批次中的剩余动作将被跳过。 `OnBatchAborted` 会触发。                   |
| **继续批次**        | 无论失败与否，调度器都会继续执行下一个动作。 `OnBatchCompleted` 在所有步骤完成时触发。 |

**停止批次示例** ——批次： `[移动到木箱, 拾取木箱]`

如果 `移动到木箱` 失败（例如，找不到 NavMesh 路径）， `拾取木箱` 会被跳过，并且 `OnBatchAborted` 触发。角色不会尝试拾取一个它从未到达的物体。

**继续批次示例** ——批次： `[挥手, 移动到木箱, 点头]`

如果 `移动到木箱` 失败，调度器仍会执行 `点头`。适用于动作彼此独立的情况。

<figure><img src="/files/fd04a444c58dd3f74de5d6c6877959de55ca2941" alt=""><figcaption></figcaption></figure>

***

## UnityEvents

`ConvaiActionDispatcher` 在其 Inspector 的 **事件** 部分中提供了一整套 UnityEvents。将它们连接到场景中的任意方法，即可响应动作生命周期事件。

### 批次事件

| 事件        | 触发时机                     | 签名   |
| --------- | ------------------------ | ---- |
| **批次开始时** | 新批次开始执行                  | `()` |
| **批次完成时** | 批次中的所有步骤都已完成（包括返回 `未处理`) | `()` |
| **批次中止时** | 由于失败而提前停止了一个批次（停止批次策略）   | `()` |

### 步骤事件

| 事件         | 触发时机                                     | 签名                         |
| ---------- | ---------------------------------------- | -------------------------- |
| **步骤开始时**  | 单个动作步骤开始                                 | `(ConvaiActionInvocation)` |
| **步骤成功时**  | 执行器返回了 `成功`                              | `(ConvaiActionInvocation)` |
| **步骤失败时**  | 执行器返回了 `失败`, `超时`，或 `Canceled`；或者定义/目标缺失 | `(ConvaiActionInvocation)` |
| **步骤未处理时** | 执行器返回了 `未处理`                             | `(ConvaiActionInvocation)` |

{% hint style="info" %}
步骤事件会接收一个 `ConvaiActionInvocation` 参数。这使你可以访问动作命令、定义、已解析的目标，以及它在批次中的第几步。可用它来构建具备上下文感知的 UI、日志或游戏玩法响应。
{% endhint %}

<figure><img src="/files/15270201fa35bb13d1231aee246b75289d72f62d" alt=""><figcaption></figcaption></figure>

### 示例：连接事件

**在批次运行时显示加载指示器：**

* `批次开始时` → `LoadingSpinner.SetActive(true)`
* `批次完成时` → `LoadingSpinner.SetActive(false)`
* `批次中止时` → `LoadingSpinner.SetActive(false)`

**当动作成功时更新任务追踪器：**

* `步骤成功时` → `QuestTracker.OnActionCompleted(ConvaiActionInvocation)`

**将失败记录到训练评分系统：**

* `步骤失败时` → `ScoreManager.OnActionFailed(ConvaiActionInvocation)`

***

## 脚本 API

如果你需要从自己的代码中触发动作，而不是等待后端响应，你可以调用 `EnqueueActions` 直接：

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

// 注入一个不带目标的“Wave”动作
dispatcher.EnqueueActions(new List<ConvaiActionCommand>
{
    new ConvaiActionCommand("Wave")
});

// 注入一个目标为“Crate”的“Move To”动作
dispatcher.EnqueueActions(new List<ConvaiActionCommand>
{
    new ConvaiActionCommand("Move To", "Crate")
});
```

{% hint style="info" %}
`EnqueueActions` 遵循相同的 **批次策略** 与后端接收的命令相同的处理方式。如果策略是 `Queue`，注入的批次会等待轮到它执行。
{% endhint %}

***

## 高级：不通过调度器接收动作

`ConvaiActionDispatcher` 处理完整的动作管线，是大多数项目的正确选择。如果你需要完全自定义的管线，可以直接订阅 `ConvaiCharacter.OnActionsReceived` 代替。

```csharp
public event Action<IReadOnlyList<ConvaiActionCommand>> OnActionsReceived;
```

每个 `ConvaiActionCommand` 包含两个字段：

| 属性          | 类型     | 说明                          |
| ----------- | ------ | --------------------------- |
| `名称`        | string | 后端选择的动作名称（例如， `"Move To"`)  |
| `目标`        | string | 原始目标名称字符串。若没有目标，则为 Null 或空。 |
| `HasTarget` | bool   | `true` 如果 `目标` 非空且不为空字符串    |

{% hint style="warning" %}
直接订阅意味着你会失去调度器的所有功能——批次策略、失败策略、自动目标解析以及 UnityEvent 接口。你需要自行处理所有内容。
{% endhint %}

### 正确订阅

请始终在 `OnDisable`中取消订阅。否则会导致过期的处理程序在组件销毁后仍被触发。

```csharp
private void OnEnable()
{
    if (_character != null)
        _character.OnActionsReceived += HandleActionsReceived;
}

private void OnDisable()
{
    if (_character != null)
        _character.OnActionsReceived -= HandleActionsReceived;
}

private void HandleActionsReceived(IReadOnlyList<ConvaiActionCommand> actions)
{
    foreach (ConvaiActionCommand command in actions)
    {
        switch (command.Name)
        {
            case "Wave":
                PlayWaveAnimation();
                break;
            case "Move To" when command.HasTarget:
                StartCoroutine(MoveToTarget(command.Target));
                break;
        }
    }
}
```

### 房间级订阅

如果你的监听器位于会话或管理器级别——例如，一个同时观察多个角色动作的训练评估管理器——请改为订阅 `ConvaiManager` 上的全房间事件：

```csharp
_convaiManager.Events.OnCharacterActionReceived += (characterId, actions) =>
{
    Debug.Log($"角色 {characterId} 收到了 {actions.Count} 个动作。");
};
```

`ConvaiManager.Events.OnCharacterActionReceived` 会针对 **房间中的所有角色触发** 并包含 `characterId`。当你的组件已经附加在 NPC 的 GameObject 上时，请使用角色级 `OnActionsReceived` 订阅。当你的逻辑是集中式的，并且需要观察多个角色时，请使用房间级事件。

### 两者一起使用

`ConvaiActionDispatcher` 和自定义订阅者可以在同一个角色上共存——二者都会接收到相同的批次。这对于在正常执行的同时进行分析或日志记录很有用：

```csharp
// 仅订阅用于日志记录——执行由调度器处理
_character.OnActionsReceived += batch =>
{
    foreach (var cmd in batch)
        analyticsService.Track("action_received", cmd.Name, cmd.Target);
};
```

***

## 结论

`ConvaiActionDispatcher` 让你可以精确控制动作批次的排序方式、冲突时的处理，以及失败如何处理。UnityEvent 接口让你无需编写调度器子类，就能对执行的每个阶段做出响应——从批次开始到单个步骤结果都包括在内。对于测试或脚本化序列， `EnqueueActions` 可让你直接从代码中注入批次。对于完全自定义的管线， `OnActionsReceived` 则提供对每个传入命令的原始访问。

下一步：编写自定义执行器——构建内置执行器未涵盖的任何行为。


---

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

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/actions/dispatcher-and-batch-policies.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
