> 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` 通过 RTVI。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` 字段：

| 字段               | 类型                              | 描述                                 |
| ---------------- | ------------------------------- | ---------------------------------- |
| `Request`        | `ConvaiNarrativeTriggerRequest` | SDK 接受的强类型请求。包含模式、线字段名称和线字段值。      |
| `TriggerName`    | `string`                        | 已保存的触发器名称。对于行内事件和脚本语音为空。           |
| `TriggerMessage` | `string`                        | 行内事件文本或 SDK 生成的脚本语音负载。对于已保存的触发器为空。 |
| `Queued`         | `bool`                          | `true` 如果由于会话尚未打开而延迟了该触发器，则为 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` 如果请求成功。                       |
| `Data` | `T`      | 获取到的数据。 `default` 如果 `成功` 是 `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 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/narrative-design/scripting-narrative-design.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.
