> 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/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`                              | 最近一次从后端接收到的章节 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
}
```

### 事件

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

{% hint style="warning" %}
`OnSectionChanged` 是位于 `OnSectionDataReceived` 通过 SDK 内部的 `EventHub`传递。如果你的订阅代码会调用 Unity API（例如 `GameObject.SetActive`），请确保 `ConvaiNarrativeDesignManager` 在场景中——它会自动使用主线程传递。对 `IConvaiNarrativeDesign` 的原始订阅可能会根据配置在后台线程中到达事件。
{% endhint %}

## 从代码中调用触发器

```csharp
// 命名触发器——沿特定边推进图谱
bool accepted = character.NarrativeDesign.InvokeTrigger("CheckpointReached");

// 带可选消息负载的命名触发器
character.NarrativeDesign.InvokeTrigger("ItemInspected", "灭火器缺少插销。");
```

`InvokeTrigger` 返回 `false` 如果 `triggerName` 是位于 `triggerMessage` 都为空，或者触发器在内部被拒绝，则返回。否则它返回 `true` ；如果会话尚未打开，则将触发器排队。

## 控制角色说什么

`InvokeSpeech` 让你可以直接控制角色的下一句发言，而不会推进叙事图谱。根据你是否将消息包裹在 `<speak>` 标签中，它有两种不同模式。

### 上下文注入（纯文本）

传入纯字符串，可让角色 *知晓* 一条信息。角色会吸收上下文，并用自己的话作出回应——具体措辞由 AI 决定。

```csharp
// 角色会知晓这一事实，并自然地回应
character.NarrativeDesign.InvokeSpeech("受训者刚刚完成了疏散演练。");
```

当你希望角色以自然、对话式的方式对游戏事件作出反应，而不是照本宣科时，请使用此方式。

### 逐字语音（speak 标签）

将消息包裹在 `<speak>` 标签中，可让角色逐字逐句说出该文本。

```csharp
// 角色会逐字说出这句话
character.NarrativeDesign.InvokeSpeech("<speak>注意：二楼的消防出口现已解锁。</speak>");
```

适用于公告、脚本台词、安全警报，或任何需要精确措辞的时刻。

### 比较

| 模式                                    | 角色的行为          |
| ------------------------------------- | -------------- |
| `InvokeSpeech("text")`                | 知晓上下文，并用自己的话回应 |
| `InvokeSpeech("<speak>text</speak>")` | 逐字说出该精确文本      |

{% hint style="info" %}
`InvokeSpeech` 无论你使用哪种模式，它都不会推进叙事图谱。若要在发送消息的同时推进图谱，请使用 `InvokeTrigger` 配合命名触发器。
{% endhint %}

### 监听触发器调用

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

`ConvaiNarrativeTriggerInvocation` 字段：

| 字段               | 类型       | 说明                        |
| ---------------- | -------- | ------------------------- |
| `TriggerName`    | `string` | 发送的触发器名称（语音时为空）。          |
| `TriggerMessage` | `string` | 可选的消息负载。                  |
| `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>` 字段：

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

## 重置状态

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

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

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

## 从代码重新配置 ConvaiNarrativeDesignTrigger

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

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

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

// 在运行时更改激活模式
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);
}
```

## 架构概览

```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, msg) bool
        +InvokeSpeech(msg) bool
        +FetchSectionsAsync() Task
        +OnSectionChanged Action
    }
    class CharacterNarrativeDesignFacade {
        -_templateKeys Dictionary
        -_pendingTriggers Queue
        +FlushPending()
    }
    class ConnectionService {
        +UpdateTemplateKeys(keys)
        +SendTrigger(name, msg)
    }

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

## 结论

`IConvaiNarrativeDesign` 在代码中暴露完整的 Narrative Design 功能——触发器调用、语音注入、模板键控制、异步数据获取以及实时章节变化事件——因此你可以将其集成到任何架构中，而不必受限于 Inspector 组件。有关将这些 API 组合到真实场景中的完整示例，请参见 [使用示例](/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/narrative-design/usage-examples.md)。如需诊断问题，请参见故障排除与诊断。


---

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