> 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/template-keys-dynamic-narrative-variables.md).

# 配置叙事模板键

设置叙事模板键，使 Convai 角色能够在其目标中引用运行时值——玩家姓名、场景参数或会话状态。

模板键是在运行时用于填充角色叙事目标中的占位符的键值对。在 [Convai 仪表板](https://convai.com) 使用花括号语法——例如： `{PlayerName}` 或 `{CurrentTask}` ——SDK 会在运行时从 Unity 发送实际值，因此角色可以在对话中自然地引用它们。这样，一张叙事图就可以服务于多个包含不同参与者或动态状态的会话，而无需每次都编辑该图。

#### 在检查器中定义键

打开 `ConvaiNarrativeDesignManager` 在检查器中并展开 **模板键** 折叠项。

点击 **+** 用于添加一项。每项都有两个字段：

| 字段    | 说明                                       |
| ----- | ---------------------------------------- |
| **键** | 占位符名称，必须与叙事设计部分中的目标中写法完全一致（区分大小写，不含花括号）。 |
| **值** | 初始值。你可以在运行时通过代码覆盖它。                      |

<figure><img src="https://2402152281-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FEtUJA212Zc1S9ACc8T4l%2Fuploads%2F1oLqJc5lARpRnWg4GsTJ%2Fimage.png?alt=media&amp;token=04e70067-f6b7-4cd3-8677-ed7193d0fa2b" alt="Template Keys foldout in the ConvaiNarrativeDesignManager Inspector with two key-value entries"><figcaption><p>ConvaiNarrativeDesignManager 中的模板键折叠项。</p></figcaption></figure>

在检查器中定义的键会在……时同步到内部控制器 `Awake` ，并在会话打开时自动发送到 Convai。你无需手动调用任何内容即可确保送达。

在播放模式下，检查器会显示一个 **发送到服务器** 按钮，点击后会立即调用 `SendTemplateKeysUpdate()`。这对于无需编写代码即可测试会话进行中值变化非常有用。

#### 在运行时更新键

可在以下对象上使用任一方法 `ConvaiNarrativeDesignManager` 从代码中更新键：

**更新单个键：**

```csharp
narrativeManager.UpdateTemplateKey("PlayerName", "Alex");
```

**更新并立即发送（一次调用）：**

```csharp
narrativeManager.UpdateAndSendTemplateKey("ScenarioPhase", "Handwashing");
```

使用 `UpdateAndSendTemplateKey` 当键更改需要在角色下一次回复中生效时使用。

**一次更新多个键：**

```csharp
narrativeManager.UpdateTemplateKeys(new Dictionary<string, string>
{
    { "PlayerName", "Alex" },
    { "Department",  "Facilities" },
    { "CompletedSteps", "3" }
});
narrativeManager.SendTemplateKeysUpdate();
```

**读取当前键字典：**

```csharp
Dictionary<string, string> current = narrativeManager.GetTemplateKeys();
```

#### 键的发送方式

```mermaid
flowchart TD
    A["UpdateAndSendTemplateKey(key, value)"] --> B[UpdateTemplateKey\n更新 _templateKeys 列表 + Controller]
    B --> C[SendTemplateKeysUpdate\nSyncTemplateKeysToController]
    C --> D[Controller.SendTemplateKeysUpdate\n触发 OnTemplateKeysUpdateRequested]
    D --> E[ConvaiCharacter.NarrativeDesign.SetTemplateKeys]
    E --> F{是否在对话中？}
    F -- 是 --> G[ConnectionService.UpdateTemplateKeys\n立即发送]
    F -- 否 --> H[_hasPendingTemplateKeySync = true\n排队等待 FlushPending]
    H --> I[会话打开 → FlushPending\n键自动发送]
```

如果角色断开并重新连接，SDK 会调用 `MarkPendingReplayAfterDisconnect` 作为内部处理，因此最新的键值会在下一次连接时重新发送。重新连接后你无需手动重新发送键。

#### 键命名规则

| 规则                     | 示例                                                      |
| ---------------------- | ------------------------------------------------------- |
| 必须与仪表板中的占位符完全匹配（区分大小写） | 仪表板： `{playerName}` → 键： `playerName` （不是 `PlayerName`) |
| 前后不能有空白字符              | `"PlayerName"` ✓ — `" PlayerName"` ✗                    |
| 键字符串不能为空               | `""` 会被静默忽略                                             |
| 值可以为空字符串               | 键 `"OptionalField"` 其值为 `""` 是有效的                       |

**良好的键名：**

| 键                      | 值示例          |
| ---------------------- | ------------ |
| `PlayerName`           | `"Maria"`    |
| `ScenarioLevel`        | `"Advanced"` |
| `CompletedCheckpoints` | `"4"`        |
| `SessionStartTime`     | `"09:15"`    |

**有问题的键名：**

| 键               | 问题                                                  |
| --------------- | --------------------------------------------------- |
| `玩家名称`          | 名称中有空格——无法匹配 `{player name}` 如果仪表板使用 `{playerName}` |
| `"PlayerName "` | 尾随空格——静默不匹配                                         |
| `""`            | 为空——忽略                                              |

{% hint style="danger" %}
模板键值会作为普通字符串通过网络发送，并可能出现在角色的对话中。请勿在模板键值中包含密码、个人身份号码、API 密钥或任何其他敏感数据。
{% endhint %}

#### 直接在角色上设置键

如果你在没有……的情况下工作 `ConvaiNarrativeDesignManager`，则可以通过角色 API 直接设置模板键：

```csharp
ConvaiCharacter character = GetComponent<ConvaiCharacter>();

// 单个键
character.NarrativeDesign.SetTemplateKey("PlayerName", "Alex");

// 多个键
character.NarrativeDesign.SetTemplateKeys(new Dictionary<string, string>
{
    { "PlayerName", "Alex" },
    { "Department", "Engineering" }
});
```

角色 API 和 Manager API 最终都会汇聚到同一个 `ConnectionService.UpdateTemplateKeys` 内部调用。你可以在同一项目中使用任一路径，但避免在同一帧中对同一个键同时调用两条路径，因为这可能会发送重复更新。

#### 下一步

{% 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 %}


---

# 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/template-keys-dynamic-narrative-variables.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.
