> 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 接收到的 section ID。如果尚未接收任何 section，则为空字符串。                                                      |
| `CurrentSectionData` | `NarrativeSectionData`                | 完整的 section 负载。包含 `SectionId`, `BehaviorTreeCode`，以及 `BehaviorTreeConstants`. `null` 直到收到第一次 section 变更为止。 |

### 监听 section 变更

请在 `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($"Section: {previousId} → {newId}");
}

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

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

#### 事件

| 事件                      | 签名                                         | 描述                                          |
| ----------------------- | ------------------------------------------ | ------------------------------------------- |
| `OnSectionChanged`      | `Action<string, string>`                   | 每次 section 切换时触发。参数： `previousId`, `newId`. |
| `OnSectionDataReceived` | `Action<NarrativeSectionData>`             | 每次 section 切换时触发，带完整负载。                     |
| `OnTriggerInvoked`      | `Action<ConvaiNarrativeTriggerInvocation>` | 在本地接受触发器或语音请求后触发（在收到 Convai 确认之前）。          |

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

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

`InvokeTrigger` 会按名称发送已保存的 Narrative Design 触发器。SDK 会修剪空白字符，拒绝空名称，并且只通过 RTVI 发送 `trigger_name` 。当请求在本地被接受且会话尚未打开时，它会返回 `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("事件文本")`          | `trigger_message` | 添加行内事件上下文，并让 Convai 自然响应。           |
| `InvokeSpeech("脚本化文本")`        | `trigger_message` | 发送精确的脚本化语音；SDK 会在内部添加 `<speak>` 标签。 |

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

#### 监听触发器调用

```csharp
character.NarrativeDesign.OnTriggerInvoked += invocation =>
{
    Debug.Log($"Trigger: {invocation.TriggerName}, Queued: {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",  "中级" }
});
```

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

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

### 获取 section 和 trigger

#### 通过角色 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
// 获取 sections
FetchResult<List<SectionData>> sections =
    await NarrativeDesignFetcher.FetchSectionsAsync(characterId);

// 获取 triggers
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）
// 不会影响 section 配置列表或 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 绑定。这在运行时无法撤销。仅当你已确认正在切换到另一个角色并且不再需要现有 section 事件绑定时才调用它。
{% endhint %}

```csharp
// 永久清除所有 section 配置（移除所有 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.
