> 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/usage-examples.md).

# 叙事设计使用示例

四个完整的 Narrative Design 示例，从单触发欢迎序列到带动态反馈的自适应多区段场景。

下面的示例展示了如何组合 `ConvaiNarrativeDesignManager`, `ConvaiNarrativeDesignTrigger`，以及 `IConvaiNarrativeDesign` 将其组成完整可运行的配置。它们按从简单到高级排序，并涵盖不同领域，以展示 Narrative Design 支持的广度。每个示例都可独立使用——从与你当前复杂度水平相符的示例开始即可。

### 示例 1：脚本化欢迎流程

**复杂度：** 初学者 | **激活模式：** 手动 | **使用功能：** 管理器、触发器（手动）、一个模板键

**场景：** 一位访客来到接待台。UI 中的“开始”按钮会发送一个单一触发器，启动体验，使角色从空闲状态进入主动欢迎环节。

#### 设置

{% stepper %}
{% step %}

#### 准备场景

添加 `ConvaiNarrativeDesignManager` 添加到角色 GameObject，并从仪表板同步各个章节。你至少需要两个章节：一个空闲章节（角色等待）和一个欢迎章节（角色开始体验）。
{% endstep %}

{% step %}

#### 在会话开始前设置访客姓名

在开始会话之前，发送一个模板键，这样角色就可以用访客的名字来称呼他：

```csharp
public class ReceptionController : MonoBehaviour
{
    [SerializeField] private ConvaiNarrativeDesignManager _narrativeManager;
    [SerializeField] private string _visitorName;

    private void Start()
    {
        _narrativeManager.UpdateAndSendTemplateKey("VisitorName", _visitorName);
    }
}
```

{% endstep %}

{% step %}

#### 添加一个手动触发器

添加 `ConvaiNarrativeDesignTrigger` 到任意 GameObject 上（它不会位于世界中——由 UI 驱动）。设置 **激活模式** 到 **手动** 并从仪表板获取/选择欢迎触发器。
{% endstep %}

{% step %}

#### 绑定 UI 按钮

在 Button 组件的 **On Click ()** 事件中，指定 `ConvaiNarrativeDesignTrigger` 并选择 `ConvaiNarrativeDesignTrigger.InvokeTrigger`.
{% endstep %}

{% step %}

#### 绑定章节事件

在管理器的 **Narrative Sections** 列表中，找到欢迎章节条目并添加一个 `OnSectionStart` 监听器。将其指向欢迎开始时场景中应发生变化的内容——例如，启用姓名牌 UI 或启动环境动画。
{% endstep %}
{% endstepper %}

**运行时会发生什么：** 玩家点击 Start → `InvokeTrigger()` 将触发器发送到后端 → 图谱切换到欢迎章节 → `OnSectionStart` 在欢迎章节入口处触发 → 角色开始欢迎。

### 示例 2：分支式对话

**复杂度：** 中级 | **激活模式：** 手动（代码驱动） | **使用功能：** 管理器、 `IConvaiNarrativeDesign`, `InvokeSpeech`、多个模板键

**场景：** 一个入门助手可以引导用户浏览三个独立的主题区域（设施、系统访问、政策）。主题选择由 UI 按钮驱动，用户可以在主题之间自由切换。每个主题之后都支持开放式后续提问。

#### 设置

在管理器中同步所有主题章节。无需 `ConvaiNarrativeDesignTrigger` 组件——触发器会直接通过 `IConvaiNarrativeDesign`.

```csharp
public class OrientationController : MonoBehaviour
{
    [SerializeField] private ConvaiCharacter _character;
    [SerializeField] private ConvaiNarrativeDesignManager _narrativeManager;
    [SerializeField] private TextMeshProUGUI _activeTopicLabel;

    private void OnEnable()
    {
        _character.NarrativeDesign.OnSectionChanged += OnSectionChanged;
    }

    private void OnDisable()
    {
        _character.NarrativeDesign.OnSectionChanged -= OnSectionChanged;
    }

    // 由 UI 按钮调用
    public void SelectTopic(string triggerName)
    {
        _character.NarrativeDesign.InvokeTrigger(triggerName);
    }

    // 由自由文本输入框的提交事件调用——纯文本上下文注入
    // 角色会用自己的话自然回应
    public void AskFollowUp(string userQuestion)
    {
        _character.NarrativeDesign.InvokeSpeech(userQuestion);
    }

    // 在需要脚本化播报时调用——角色会逐字说出这段内容
    public void AnnounceToUser(string announcement)
    {
        _character.NarrativeDesign.InvokeSpeech($"<speak>{announcement}</speak>");
    }

    private void OnSectionChanged(string previous, string next)
    {
        // 更新面包屑 UI——从管理器中查找可读名称
        if (_narrativeManager.FindSectionConfig(next) is { } cfg)
            _activeTopicLabel.text = cfg.SectionName;
    }
}
```

在 Inspector 中为按钮分配触发器名称： `"TopicFacilities"`, `"TopicSystemsAccess"`, `"TopicPolicies"`.

设置 `将所有触发器的 TriggerOnce 设为 false` ，这样用户就可以重新访问任何主题。在会话开始前，从表单中发送模板键（`UserName`, `Department`）使用 `UpdateTemplateKeys`.

`InvokeSpeech` 有两种模式：纯文本会让角色用自己的话自然回应（适用于自由文本后续提问）；将消息包裹在 `<speak>` 标签中会让角色逐字说出该文本（适用于脚本化播报或精确提示）。这两种模式都不会推进图谱。参见 [控制角色语音](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/narrative-design/scripting-narrative-design.md#control-character-speech) 以获取完整参考。

### 示例 3：接近触发的展品导览

**复杂度：** 中级 | **激活模式：** 接近 | **使用功能：** 管理器、触发器（接近）、区域事件、批量模板键

**场景：** 一个产品展示厅有五个展示站点。当访客走近每个站点时，一名主持角色会开始讲解该产品。每个站点彼此独立，可按任意顺序参观。

#### 设置

为每个站点创建一个 `ConvaiNarrativeDesignTrigger` 。对于每个站点：

* **激活模式：** `接近`
* **接近半径：** 按站点大小调整（在 Scene 视图中显示为绿色球体 gizmo）
* **只触发一次：** `true`
* **场景加载时重置：** `true` （这样每次参观都会重置导览）

通过模板键传入站点特定上下文。从一个 `ScriptableObject` 在 `Start()`:

```csharp
[CreateAssetMenu(menuName = "Showroom/Station Data")]
public class StationData : ScriptableObject
{
    public string ProductName;
    public string LaunchYear;
    public string KeyFeature;
}

public class StationController : MonoBehaviour
{
    [SerializeField] private ConvaiNarrativeDesignManager _narrativeManager;
    [SerializeField] private StationData _data;

    private void Start()
    {
        _narrativeManager.UpdateTemplateKeys(new Dictionary<string, string>
        {
            { "ProductName", _data.ProductName },
            { "LaunchYear",  _data.LaunchYear  },
            { "KeyFeature",  _data.KeyFeature  }
        });
    }
}
```

使用 `OnPlayerEnterZone` 来高亮产品模型（例如，启用轮廓着色器）。使用 `OnPlayerExitZone` 来移除高亮。

{% hint style="warning" %}
如果站点的接近半径重叠，两个触发器可能会在同一帧触发，在后端响应第一个触发器之前就发送两次图谱转换。请拉开站点间距，避免接近区域相交，或者使用 Collision 模式并让触发器碰撞体在物理上分离。
{% endhint %}

### 示例 4：带动态反馈的自适应场景

**复杂度：** 高级 | **激活模式：** 代码驱动 | **使用功能：** 管理器、 `IConvaiNarrativeDesign`, `OnSectionDataReceived`, `BehaviorTreeConstants`、动态模板键、重试流程

**场景：** 一个技术技能评估器运行着一个多步骤场景。每完成一步，学习者的表现都会被评分。角色的指导程度和场景难度会根据当前累计分数动态调整。该场景可以重新尝试，并重置所有状态。

#### 会话生命周期

```mermaid
flowchart TD
    A[会话开始] --> B["UpdateAndSendTemplateKey\n(DifficultyLevel, Foundation)"]
    B --> C[角色进入对话\n→ FlushPending → keys sent]
    C --> D[场景开始\n第一个章节激活]
    D --> E[学习者完成步骤\n→ 由 ScenarioController 评分]
    E --> F["UpdateAndSendTemplateKey\n(DifficultyLevel, score > 80 ? Advanced : Foundation)"]
    F --> G[InvokeTrigger StepCompleted]
    G --> H[章节变化\n→ OnSectionDataReceived]
    H --> I[反序列化 BehaviorTreeConstants\n应用场景参数]
    I --> E
    E --> J{最后一步？}
    J -- 是 --> K[OnSectionStart Assessment Complete\n→ 显示分数摘要]
    K --> L{重新尝试？}
    L -- 是 --> M[ResetController + FetchAndSyncFromBackend\n→ 重新开始]
    L -- 否 --> N[结束会话]
```

#### 实现

```csharp
public class ScenarioController : MonoBehaviour
{
    [SerializeField] private ConvaiNarrativeDesignManager _narrativeManager;
    [SerializeField] private ConvaiCharacter _character;

    private int _totalScore;
    private int _stepCount;

    private void OnEnable()
    {
        _character.NarrativeDesign.OnSectionDataReceived += OnSectionDataReceived;
    }

    private void OnDisable()
    {
        _character.NarrativeDesign.OnSectionDataReceived -= OnSectionDataReceived;
    }

    public void StartScenario(string learnerName)
    {
        _totalScore = 0;
        _stepCount  = 0;
        _narrativeManager.UpdateTemplateKeys(new Dictionary<string, string>
        {
            { "LearnerName",    learnerName   },
            { "DifficultyLevel", "Foundation" }
        });
        _narrativeManager.SendTemplateKeysUpdate();
    }

    public void OnStepCompleted(int stepScore)
    {
        _totalScore += stepScore;
        _stepCount++;

        string level = (_totalScore / _stepCount) > 80 ? "Advanced" : "Foundation";
        _narrativeManager.UpdateAndSendTemplateKey("DifficultyLevel", level);
        _character.NarrativeDesign.InvokeTrigger("StepCompleted");
    }

    private void OnSectionDataReceived(NarrativeSectionData data)
    {
        if (string.IsNullOrEmpty(data.BehaviorTreeConstants)) return;

        // BehaviorTreeConstants 是在 Convai 仪表板中编写的 JSON 字符串
        // 包含该章节的场景特定参数
        var constants = JsonUtility.FromJson<ScenarioConstants>(data.BehaviorTreeConstants);
        ApplyScenarioParameters(constants);
    }

    public async void RetakeScenario()
    {
        _narrativeManager.ResetController();
        await _narrativeManager.FetchAndSyncFromBackendAsync();
        StartScenario("LearnerName");
    }

    private void ApplyScenarioParameters(ScenarioConstants constants)
    {
        // 将仪表板中编写的常量值应用到你的场景中
    }

    [Serializable]
    private class ScenarioConstants
    {
        public float TimeLimit;
        public int   RequiredScore;
        public bool  ShowHints;
    }
}
```

`BehaviorTreeConstants` 是你在 Convai 仪表板中按章节编写的 JSON 字符串。它携带你希望从图谱注入到 Unity 的任何数据——时间限制、评分阈值、提示标志或其他场景参数。SDK 会在每个 `NarrativeSectionData` 载荷中交付它。

{% hint style="info" %}
`BehaviorTreeCode` 和 `BehaviorTreeConstants` 在 SDK 侧是只读的。它们携带在 Convai 仪表板中编写的数据，且不能在 Unity 运行时修改。
{% endhint %}

### 下一步

{% content-ref url="/pages/e2aeb655628e9664d3a8c2993eb219a88fcfeef0" %}
[叙事设计脚本参考](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/narrative-design/scripting-narrative-design.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/usage-examples.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.
