> 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/narrative-design/scripting-narrative-design.md).

# 叙事设计脚本参考

从代码控制角色叙事流程的参考，涵盖区段事件、触发器调用、脚本化语音和数据获取。

Inspector 工作流覆盖了大多数使用场景。本页记录了完整的 C# 接口，适用于你需要程序化控制的情况——动态角色切换、运行时异步数据获取、运行时生成叙事流程，或与你自己的游戏系统进行深度集成。

此处描述的所有能力都可通过 `IConvaiNarrativeDesign`访问，所有 `ConvaiCharacter` 都通过 `NarrativeDesign` 属性暴露。 `ConvaiNarrativeDesignManager` 和 `ConvaiNarrativeDesignTrigger` 这两者在内部都会委托给该接口，因此你在 Inspector 中配置的所有内容也都可以从代码中访问。

### 访问角色 API

每个 `ConvaiCharacter` 提供一个 `NarrativeDesign` 属性，返回一个 `IConvaiNarrativeDesign` 实现：

```csharp
ConvaiCharacter character = GetComponent<ConvaiCharacter>();
IConvaiNarrativeDesign narrative = character.NarrativeDesign;
```

#### 属性

| 属性                   | 类型                                    | 说明                                                                                             |
| -------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `TemplateKeys`       | `IReadOnlyDictionary<string, string>` | 当前为该角色跟踪的所有模板键快照。                                                                              |
| `CurrentSectionId`   | `string`                              | 最近一次从 Convai 接收到的章节 ID。若尚未接收任何章节，则为空字符串。                                                       |
| `CurrentSectionData` | `NarrativeSectionData`                | 完整的章节载荷。包含 `SectionId`, `BehaviorTreeCode`，以及 `BehaviorTreeConstants`. `null` ，直到接收到第一次章节变更为止。 |

### 监听章节变更

请在 `OnEnable` 中订阅，并在 `OnDisable` 中订阅这些事件，以避免组件被禁用或销毁后留下过期监听器。

```csharp
private void OnEnable()
{
    character.NarrativeDesign.OnSectionChanged     += HandleSectionChanged;
    character.NarrativeDesign.OnSectionDataReceived += HandleSectionData;
}

private void OnDisable()
{
    character.NarrativeDesign.OnSectionChanged     -= HandleSectionChanged;
    character.NarrativeDesign.OnSectionDataReceived -= HandleSectionData;
}

private void HandleSectionChanged(string previousId, string newId)
{
    Debug.Log($"章节：{previousId} → {newId}");
}

private void HandleSectionData(NarrativeSectionData data)
{
    Debug.Log($"章节 ID：{data.SectionId}");
    // 此处可使用 data.BehaviorTreeCode 和 data.BehaviorTreeConstants
}
```

这些事件通过 SDK 内部的 `EventHub`传递。如果你的处理器会调用 Unity API（例如 `GameObject.SetActive`），请在场景中使用 `ConvaiNarrativeDesignManager` ——它会自动执行主线程分发。对 `IConvaiNarrativeDesign` 的原始订阅可能会根据配置在后台线程上到达。

#### 事件

| 事件                      | 签名                                         | 说明                                    |
| ----------------------- | ------------------------------------------ | ------------------------------------- |
| `OnSectionChanged`      | `Action<string, string>`                   | 在每次章节切换时触发。参数： `previousId`, `newId`. |
| `OnSectionDataReceived` | `Action<NarrativeSectionData>`             | 在每次章节切换时触发，并携带完整载荷。                   |
| `OnTriggerInvoked`      | `Action<ConvaiNarrativeTriggerInvocation>` | 在触发器或语音请求被本地接受后触发（在 Convai 确认之前）。     |

### 从代码中调用触发器

```csharp
// 已保存的触发器 — 沿着特定边推进图表
bool accepted = character.NarrativeDesign.InvokeTrigger("CheckpointReached");
```

`InvokeTrigger` 按名称发送一个已保存的 Narrative Design 触发器。SDK 会去除空白字符，拒绝空名称，并且只通过 `trigger_name` 发送 RTVI。若请求在本地被接受且会话尚未打开，则返回 `true` ，并将触发器排队。

使用 `InvokeEvent` 当你想发送上下文事件文本而不是已保存的图表触发器时使用：

```csharp
character.NarrativeDesign.InvokeEvent("灭火器缺少保险销。");
```

`InvokeEvent` 仅通过 `trigger_message` 发送。Convai 会将该消息视为内联上下文并自然响应；它不会按名称选择已保存的触发器。

### 控制角色语音

`InvokeSpeech` 发送精确的脚本化台词，而不会推进叙事图表。传入你希望角色说出的文本；SDK 会在发送前将其包装为 `<speak>...</speak>` ，然后再发送 `trigger_message`.

```csharp
character.NarrativeDesign.InvokeSpeech("注意：二层的火灾出口现在已解锁。");
```

请不要在 Unity 代码中包含 `<speak>` 标签。对于 Convai 应自行决定措辞的上下文事件，请使用 `InvokeEvent` 。

| 方法                              | 字段映射              | 运行时行为                                   |
| ------------------------------- | ----------------- | --------------------------------------- |
| `InvokeTrigger("TriggerName")`  | `trigger_name`    | 调用一个已保存的 Narrative Design 触发器，并且可以推进图表。 |
| `InvokeEvent("event text")`     | `trigger_message` | 添加内联事件上下文，并让 Convai 自然回应。               |
| `InvokeSpeech("scripted text")` | `trigger_message` | 发送精确的脚本化台词；SDK 会在内部添加 `<speak>` 标签。     |

{% hint style="info" %}
只有已保存的触发器会按名称推进图表。内联事件和脚本化语音使用 `trigger_message` ，并且不会发送 `trigger_name`.
{% endhint %}

#### 监听触发器调用

```csharp
character.NarrativeDesign.OnTriggerInvoked += invocation =>
{
    Debug.Log($"触发器：{invocation.TriggerName}，已排队：{invocation.Queued}");
};
```

`ConvaiNarrativeTriggerInvocation` 字段：

| 字段               | 类型                              | 说明                                  |
| ---------------- | ------------------------------- | ----------------------------------- |
| `请求`             | `ConvaiNarrativeTriggerRequest` | 由 SDK 接受的类型化请求。包含模式、字段映射名和字段映射值。    |
| `TriggerName`    | `string`                        | 已保存的触发器名称。对于内联事件和脚本化语音为空。           |
| `TriggerMessage` | `string`                        | 内联事件文本或 SDK 生成的脚本化语音载荷。对于已保存的触发器为空。 |
| `Queued`         | `bool`                          | `true` 如果由于会话尚未打开而延迟了该触发器。          |

### 通过代码设置模板键

```csharp
// 设置单个键
character.NarrativeDesign.SetTemplateKey("PlayerName", "Alex");

// 设置多个键
character.NarrativeDesign.SetTemplateKeys(new Dictionary<string, string>
{
    { "PlayerName",  "Alex" },
    { "ScoreLevel",  "Intermediate" }
});
```

如果会话已打开，这两种方法会立即发送；如果尚未打开，则会排队等待下一次连接。

角色级 API 和 `ConvaiNarrativeDesignManager`的方法在内部会汇聚到同一传输层。当你希望这些键在 Inspector 中可见且可编辑时，请使用 Manager 的方法；当你进行纯代码驱动流程且不需要 Inspector 可见性时，请使用角色 API。

### 获取章节和触发器

#### 通过角色 API

```csharp
NarrativeFetchResult<List<NarrativeSectionInfo>> result =
    await character.NarrativeDesign.FetchSectionsAsync();

if (result.Success)
{
    foreach (NarrativeSectionInfo section in result.Data)
        Debug.Log($"{section.SectionId}：{section.SectionName}");
}
else
{
    Debug.LogError(result.Error);
}
```

```csharp
NarrativeFetchResult<List<NarrativeTriggerInfo>> result =
    await character.NarrativeDesign.FetchTriggersAsync();

foreach (NarrativeTriggerInfo trigger in result.Data)
    Debug.Log($"{trigger.TriggerName} → {trigger.DestinationSection}");
```

`NarrativeSectionInfo` 字段： `SectionId`, `SectionName`.

`NarrativeTriggerInfo` 字段： `TriggerId`, `TriggerName`, `TriggerMessage`, `DestinationSection`.

#### 通过静态获取器

`NarrativeDesignFetcher` 提供相同的数据，而无需角色组件引用——在编辑器工具或加载界面中很有用：

```csharp
// 获取章节
FetchResult<List<SectionData>> sections =
    await NarrativeDesignFetcher.FetchSectionsAsync(characterId);

// 获取触发器
FetchResult<List<TriggerData>> triggers =
    await NarrativeDesignFetcher.FetchTriggersAsync(characterId);

// 并行获取两者
var (sectionsResult, triggersResult) =
    await NarrativeDesignFetcher.FetchAllAsync(characterId);
```

`FetchResult<T>` 字段：

| 字段   | 类型       | 说明                               |
| ---- | -------- | -------------------------------- |
| `成功` | `bool`   | `true` 如果请求成功。                   |
| `数据` | `T`      | 获取到的数据。 `默认值` 如果 `成功` 是 `false`. |
| `错误` | `string` | 错误消息。 `null` 如果 `成功` 是 `true`.   |

### 高级运行时控制

#### 重置控制器状态

```csharp
// 仅重置控制器状态（清除 CurrentSectionID 和 CurrentSectionData）
// 不会触碰章节配置列表或 Unity Event 绑定
narrativeManager.ResetController();
```

#### 从代码重新配置 ConvaiNarrativeDesignTrigger

所有可在 Inspector 中配置的设置都对应有 setter 方法：

```csharp
ConvaiNarrativeDesignTrigger trigger = GetComponent<ConvaiNarrativeDesignTrigger>();

// 覆盖触发器选择
trigger.SetTrigger("trigger-uuid", "CheckpointA");

// 在运行时更改激活模式
trigger.SetActivationMode(TriggerActivationMode.Proximity);
trigger.SetProximityRadius(5f);

// 提供一个已知的玩家 Transform（当自动查找不足时很有用）
trigger.SetPlayerTransform(playerController.transform);

// 切换目标角色
trigger.SetCharacter(otherCharacter.GetComponent<IConvaiCharacterAgent>());

// 在关键触发器前进行验证
if (!trigger.ValidateConfiguration())
{
    foreach (string warning in trigger.ValidationWarnings)
        Debug.LogWarning(warning);
}
```

{% hint style="warning" %}
`ClearAllSectionConfigs()` 会移除所有 `UnitySectionEventConfig` 条目以及所有 Unity Event 绑定。此操作在运行时无法撤销。仅在你确认正在切换到另一个角色且不再需要现有章节事件绑定时调用。
{% endhint %}

```csharp
// 永久清除所有章节配置（移除所有 UnitySectionEventConfig 条目）
// 仅在切换到完全不同的角色时使用
narrativeManager.ClearAllSectionConfigs();
```

### 组件关系

```mermaid
classDiagram
    class ConvaiNarrativeDesignManager {
        +UpdateTemplateKey(key, value)
        +FetchAndSyncFromBackend()
        +OnAnySectionChanged UnityEvent
    }
    class ConvaiNarrativeDesignTrigger {
        +InvokeTrigger() bool
        +ResetTrigger()
        +ValidateConfiguration() bool
    }
    class IConvaiNarrativeDesign {
        +SetTemplateKey(key, value) bool
        +InvokeTrigger(name) bool
        +InvokeEvent(message) bool
        +InvokeSpeech(text) bool
        +FetchSectionsAsync() Task
        +OnSectionChanged Action
    }
    class CharacterNarrativeDesignFacade {
        -_templateKeys Dictionary
        -_pendingTriggers Queue
        +FlushPending()
    }
    class ConnectionService {
        +UpdateTemplateKeys(keys)
        +SendNarrativeTrigger(request)
    }

    ConvaiNarrativeDesignManager ..> IConvaiNarrativeDesign ：委托给
    ConvaiNarrativeDesignTrigger ..> IConvaiNarrativeDesign ：调用 InvokeTrigger
    IConvaiNarrativeDesign <|.. CharacterNarrativeDesignFacade
    CharacterNarrativeDesignFacade --> ConnectionService ：通过 RTVI 发送
```

`ConvaiNarrativeDesignManager` 和 `ConvaiNarrativeDesignTrigger` 都委托给 `IConvaiNarrativeDesign`.  `CharacterNarrativeDesignFacade` 实现该接口并管理待处理队列； `ConnectionService` 处理实际的 RTVI 传输。

### 下一步

{% content-ref url="/pages/5a4803abb898897932fbc643bd96c9e58ef4d96c" %}
[叙事设计使用示例](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/narrative-design/usage-examples.md)
{% endcontent-ref %}

{% content-ref url="/pages/bd435d6564aabc01fda5fe6b7c130cf0ffeb1c36" %}
[排查叙事设计问题](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/narrative-design/troubleshooting-and-diagnostics.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/narrative-design/scripting-narrative-design.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.
