Skip to content

场景跳转与条目切换

点击按钮 → 跳转到另一段预写的开场。在文本框中输入 → 改变条目对 AI 说的内容。本教程将向你展示这两种用法。


第一部分:通过按钮切换不同开场

你将构建什么

一个拥有多个预写开场的世界。玩家首先看到「主开场」,下方有可点击的按钮。当他们点击其中一个时,聊天中的第一条消息会立即切换到另一段预写开场——不需要 AI 生成,只是你写好的文本。

工作原理

在 Yumina 中,你可以在编辑器的第一条消息标签页创建多个问候语。当玩家开始新会话时,所有问候语会被打包为第一条消息上的滑动切换(左右滑动切换)。玩家已经可以手动滑动了——但我们想要的是:让玩家通过一次按钮点击就跳转到特定的问候语

这就是 switchGreeting(index) API 的用途——它让自定义组件通过代码直接跳转到第 N 个问候语。

玩家点击「进入黑暗洞穴」
  → 代码调用 api.switchGreeting(1)
  → 第一条消息切换到第 2 个问候语(index 从 0 开始,所以 1 = 第二个)
  → 玩家立即看到你预写的黑暗洞穴开场

分步教程

第 1 步:在第一条消息标签页创建多个问候语

打开编辑器,点击左侧边栏的第一条消息标签页。

这个标签页专门用于管理开场内容。你可以创建多个问候语——每个都会变成一个滑动页。

创建第一个问候语(主开场——展示路线选择):

点击「创建第一条消息」。在文本框中写下主开场。这是玩家打开会话时首先看到的内容——描述场景并引导他们做出选择:

*你在一片神秘的森林中醒来。晨雾在古老的树木之间缭绕。*

两条道路在你面前分叉:

**左边** — 一条通往黑暗的狭窄小径。冰冷的空气和远处的回声。

**右边** — 一条洒满阳光的小路,野花遍地,鸟鸣声声。

你会走哪条路?

为什么只描述场景而不让 AI 回复?因为问候语是你预先写好的固定文本,不是 AI 生成的。你可以精确控制玩家看到的每一个字。

创建第二个问候语(黑暗洞穴开场):

点击底部的「添加问候语」。你会看到编号标签 12 出现。点击 2 切换到第二个问候语的编辑框。写下黑暗洞穴路线的开场:

*你踏上左边的小路。头顶的树冠越来越密,吞噬了光线。几分钟后,小径缩窄成岩壁上的一道裂缝——一个洞穴的入口。*

*冰冷的空气涌出,带着潮湿石头和某种金属味的气息。微弱的蓝绿色光在深处闪烁——生物发光的真菌攀附在洞壁上。*

*你深吸一口气走了进去。身后,最后一缕阳光缩成一条苍白的线,然后消失了。*

你孤身一人在黑暗中。

这段文字只有在玩家点击「进入黑暗洞穴」后才会显示。在此之前,玩家看到的是第一个问候语(主开场)。

创建第三个问候语(阳光草原开场):

再次点击「添加问候语」。切换到标签 3,写下阳光草原路线的开场:

*你选择了右边的小路。树木渐渐稀疏,温暖的阳光透过树冠倾泻而下。几分钟后,森林开阔成一片延伸到地平线的广袤草原。*

*各种颜色的野花在微风中轻轻摇曳。远处一条小溪闪闪发光。附近某处,一只鸟唱着你从未听过的旋律。*

*你感觉肩膀上的紧绷感融化了。不管这个地方是什么,它让人感到安全。*

欢迎来到永花草原。

问候语的顺序就是 index

底部编号标签的顺序就是 switchGreeting()index 参数。标签 1 = index 0(默认显示),标签 2 = index 1,标签 3 = index 2。你在写按钮代码时会用到这个 index。

现在你有了 3 个问候语。保存世界后,新会话默认显示第一个(主开场)。接下来我们要做的是创建按钮,让玩家可以点击跳转到第二个或第三个。


第 2 步:创建一个路线追踪变量

我们需要一个变量来记录「玩家选择了哪条路线」。这个变量有两个用途:

  • 选择后让按钮消失(TSX 代码检查这个变量——如果不是 "none",就不显示按钮)
  • 让后续对话知道当前路线(行为规则可以根据这个变量切换知识条目)

编辑器 → 左侧边栏 → 变量标签页 → 点击「添加变量」

字段填写内容原因
显示名称Current Route供你自己参考
IDcurrent_route代码通过这个 ID 读写变量
类型String因为值是文本("none""dark""light"
默认值none表示「尚未选择」。按钮代码检查这个值
分类Tag只是一个分类标签,方便在变量列表中查找
行为规则Do not modify this variable. It is controlled by the player's UI choice.告诉 AI 不要修改这个变量——只有按钮可以

行为规则字段是给 AI 的指令。如果你不写,AI 可能会自行决定改变这个变量的值(例如,AI 认为「玩家走进了洞穴」就自己把 current_route 设为 "dark")。一旦你写了规则,AI 就不会触碰它。


第 3 步:(可选)创建知识条目和行为规则

如果你希望 AI 在选择路线后的回复中引用不同的世界设定,就执行这一步。如果你只想切换开场文本而不需要后续世界变化,可以跳过。

创建两个知识条目(默认禁用):

编辑器 → 条目标签页 → 创建新条目

黑暗洞穴知识条目:

字段填写内容原因
名称Dark Cave Lore供你自己参考
段落System Presets预设段落中的条目每次都会发送给 AI
启用(关闭开关)默认禁用——在玩家选择黑暗路线后,行为规则会启用它

内容:

[World Setting: Shadowmaw Cave]
The player is exploring Shadowmaw Cave. Key details:
- Ancient dwarven ruins, abandoned for centuries
- Bioluminescent fungi provide faint blue-green light
- Strange creatures lurk in the deeper tunnels
- Temperature drops the further in you go

Maintain a tense horror-survival atmosphere. Describe echoing sounds, flickering shadows, water dripping, and the oppressive weight of stone overhead.

阳光草原知识条目: 创建另一个条目,同样默认禁用,内容描述草原的环境和氛围。

为什么默认禁用? 因为在玩家选择路线之前,两个世界设定都不应该影响 AI。只有在玩家选择后,行为规则才会启用匹配的那个并禁用另一个。

创建两个行为规则:

编辑器 → 行为标签页 → 添加行为

行为 1:「选择黑暗路线」

字段填写内容原因
名称Choose Dark Route供你自己参考
触发器选择「Action」→ Action ID choose-dark当 TSX 代码调用 executeAction("choose-dark") 时触发

然后在「执行动作」下,按顺序添加:

动作类型设置效果
修改变量current_route 设为 dark记录玩家选择了黑暗路线
启用条目Dark Cave Lore开启黑暗洞穴设定
禁用条目Sunlit Meadow Lore关闭草原设定(防止两个同时生效)

行为 2:「选择光明路线」 — 以相同方式创建。Action ID 是 choose-light,动作相反(启用草原知识,禁用洞穴知识)。

为什么不直接在 TSX 代码中用 setVariable 因为 setVariable 只能改变变量——不能切换条目的启用/禁用状态。行为的「启用条目」/「禁用条目」动作才是在运行时启用/禁用条目的方式。所以当按钮被点击时,我们同时做三件事:setVariable(改变变量)+ executeAction(触发行为来切换条目)+ switchGreeting(切换开场)。


第 4 步:在 Root Component 中添加路线选择按钮

这是让按钮出现在聊天界面中的关键步骤。

编辑器 → Custom UI 部分 → 打开 index.tsx → 粘贴以下代码(替换默认代码):

tsx
export default function MyWorld() {
  const api = useYumina();
  const hasChosen = api.variables.current_route !== "none";

  return (
    <Chat renderBubble={(msg) => (
      <div>
        {/* 正常渲染消息文本 */}
        <div
          style={{ color: "#e2e8f0", lineHeight: 1.7 }}
          dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
        />

        {/* 路线选择按钮 */}
        {/* msg.messageIndex === 0 表示只在第一条消息上显示 */}
        {/* !hasChosen 表示选择后隐藏 */}
        {msg.messageIndex === 0 && !hasChosen && (
          <div style={{
            display: "flex",
            gap: "12px",
            marginTop: "16px",
          }}>
            <button
              onClick={() => {
                api.setVariable("current_route", "dark");   // 记录选择,让按钮消失
                api.executeAction("choose-dark");            // 触发行为规则来切换知识条目
                api.switchGreeting?.(1);                     // 切换到第 2 个问候语
              }}
              style={{
                flex: 1,
                padding: "16px",
                background: "linear-gradient(135deg, #1e1b4b, #312e81)",
                border: "1px solid #4338ca",
                borderRadius: "12px",
                color: "#c7d2fe",
                fontSize: "15px",
                fontWeight: "bold",
                cursor: "pointer",
              }}
            >
              Enter the Dark Cave
            </button>

            <button
              onClick={() => {
                api.setVariable("current_route", "light");
                api.executeAction("choose-light");
                api.switchGreeting?.(2);                     // 切换到第 3 个问候语
              }}
              style={{
                flex: 1,
                padding: "16px",
                background: "linear-gradient(135deg, #365314, #4d7c0f)",
                border: "1px solid #65a30d",
                borderRadius: "12px",
                color: "#ecfccb",
                fontSize: "15px",
                fontWeight: "bold",
                cursor: "pointer",
              }}
            >
              Walk to the Sunlit Meadow
            </button>
          </div>
        )}
      </div>
    )} />
  );
}

逐行说明:

  • <Chat renderBubble={...} /> — 使用平台默认的聊天界面(输入框、滑动切换、存档点都是内置的),你只接管气泡的渲染方式
  • const api = useYumina() — 获取 Yumina 的 API,可以读取变量、写入变量、触发动作、切换问候语
  • api.variables.current_route — 读取当前路线变量的值
  • hasChosen — 如果不是 "none",说明玩家已经做了选择
  • msg.contentHtml — renderBubble 传入的预渲染 HTML(Markdown 已经处理过了)
  • msg.messageIndex === 0 — 只在第一条消息上显示按钮(不是每条消息都显示)
  • !hasChosen — 选择后按钮消失
  • api.setVariable("current_route", "dark") — 将变量设为 "dark",使 hasChosen 变为 true,按钮消失
  • api.executeAction("choose-dark") — 触发我们在第 3 步创建的行为规则
  • api.switchGreeting?.(1) — 将第一条消息切换到 index 1(第二个问候语)。?. 是可选链——如果 API 不可用,不会报错

不想自己写代码?使用 Studio AI

编辑器顶部 → 点击「进入 Studio」→ AI 助手面板 → 用自然语言描述你想要的效果,AI 会帮你生成代码。


第 5 步:保存并测试

  1. 点击编辑器顶部的「保存」
  2. 点击「开始游戏」或返回主页开始新会话
  3. 你会看到主开场,下方有两个按钮
  4. 点击「Enter the Dark Cave」——第一条消息立即变为你预写的洞穴开场,按钮消失
  5. 向 AI 发送几条消息——如果你完成了第 3 步,AI 的回复会受到洞穴知识的影响
  6. 想测试另一条路线?返回主页开始新会话,这次点击另一个按钮

故障排除:

症状可能原因解决方法
看不到按钮Root Component 代码未保存或有语法错误检查 Custom UI 部分底部的编译状态——应该显示绿色的「OK」
点击按钮没反应switchGreeting 尚未部署到服务器确保你使用的是最新版本
按钮点击了但开场没切换问候语数量不够确认第一条消息标签页中有 3 个问候语
按钮点击了但没消失变量设置不正确检查编辑器——变量的默认值是否为 none,Root Component 代码是否正确检查了 current_route
知识没有切换行为规则配置错误验证行为的 Action ID 是否与代码匹配(choose-dark / choose-light

第二部分:玩家输入修改条目内容

你将构建什么

在聊天界面中添加一个文本输入框。玩家在里面输入内容(例如自定义规则、角色名称或故事指令)。点击「应用」后,文本会被注入到知识条目中——改变 AI 下次看到的内容。

工作原理

Yumina 的条目支持宏语法。你可以在条目内容中写 {{variableId}}——这是一个占位符。每次引擎构建发送给 AI 的提示词时,会自动用变量的当前值替换占位符。

例如:

  • 你在条目中写:Special rule: {{custom_rule}}
  • 变量 custom_rule 的值是 "All magic is allowed"
  • AI 收到的提示词中,这一行被改写为:Special rule: All magic is allowed

关键点:替换不是实时的。 它发生在每次构建提示词时——也就是当玩家发送下一条消息、AI 即将回复的时候。

完整时序:

1. 条目内容写着:"Special rule: {{custom_rule}}"
2. 变量 custom_rule 的当前值 = "All magic is allowed"
3. 玩家发送消息 → 引擎构建提示词 → 替换 {{custom_rule}} 为变量值
   → AI 收到 "Special rule: All magic is allowed" → AI 据此回复

4. 玩家在输入框中输入 "Magic is forbidden",点击「应用」
5. 代码调用 setVariable("custom_rule", "Magic is forbidden")
   → 变量值立即更新
6. 但 AI 还不知道!提示词还没有重新构建。

7. 玩家发送另一条消息 → 引擎重新构建提示词 → 这次使用新值
   → AI 收到 "Special rule: Magic is forbidden" → AI 开始遵守新规则

一句话总结:改变变量是即时的,但 AI 在下一条消息时才能看到变化。

分步教程

第 1 步:创建一个字符串变量

这个变量保存玩家输入的内容。

编辑器 → 变量标签页 → 「添加变量」

字段填写内容原因
显示名称Custom Rule供你自己参考
IDcustom_rule条目中的 {{custom_rule}} 宏会查找这个 ID
类型String因为内容是玩家输入的任意文本
默认值(留空,或设置默认值如 All magic is allowed空 = 新会话没有规则;非空 = 有一个起始规则
行为规则Do not modify this variable. It is set by the player via UI.告诉 AI 不要自行修改这个变量

第 2 步:在条目中使用宏

现在创建一个使用 {{custom_rule}} 作为占位符的条目。引擎在构建提示词时会自动替换它。

编辑器 → 条目标签页 → 创建新条目

字段填写内容原因
名称World Rules供你自己参考
段落System Presets预设段落中的条目每次都会发送给 AI

内容:

[World Rules]
The following rule is in effect for this world and must be respected at all times:
{{custom_rule}}

发生了什么? 每次引擎构建提示词时,会扫描所有条目内容中的 {{...}}。如果大括号内的内容匹配某个变量 ID,该变量的当前值就会替换它。所以 {{custom_rule}} 会被变量 custom_rule 的值替换。

如果变量为空,该行就变成空的——AI 看到「The following rule is in effect...」后面什么都没有。如果值是「Magic is forbidden」,AI 看到的就是「The following rule is in effect... Magic is forbidden」。


第 3 步:在 Root Component 中添加输入框

我们希望在聊天界面中有一个输入框,让玩家可以输入新规则。这个输入框写在 Root Component 的 renderBubble 中,只显示在最后一条消息下方(避免每条消息下都出现一个输入框)。

在你的 index.tsx 中,添加以下内容。如果你已经有了第一部分的代码,只需在 JSX renderBubble 返回的内容中,消息文本下方添加:

tsx
// 在 MyWorld() 顶部附近(<Chat> 外面),添加这些
const api = useYumina();                                    // 如果已经有了,不要重复
const msgs = api.messages || [];
const [ruleInput, setRuleInput] = React.useState("");
const currentRule = String(api.variables.custom_rule || "");

// 在 renderBubble 内部,添加检查
const isLastMsg = msg.messageIndex === msgs.length - 1;    // 是否是最后一条消息

// 在 renderBubble 返回的 JSX 中,消息文本下方
{isLastMsg && (
  <div style={{
    marginTop: "12px",
    padding: "12px",
    background: "rgba(30,41,59,0.5)",
    borderRadius: "8px",
    border: "1px solid #334155",
  }}>
    <div style={{ fontSize: "12px", color: "#94a3b8", marginBottom: "6px" }}>
      World Rule: {currentRule || "(not set)"}
    </div>
    <div style={{ display: "flex", gap: "8px" }}>
      <input
        type="text"
        value={ruleInput}
        onChange={(e) => setRuleInput(e.target.value)}
        placeholder="Type a new rule..."
        style={{
          flex: 1, padding: "6px 10px", background: "#1e293b",
          border: "1px solid #475569", borderRadius: "6px",
          color: "#e2e8f0", fontSize: "13px", outline: "none",
        }}
        onKeyDown={(e) => {
          if (e.key === "Enter" && ruleInput.trim()) {
            api.setVariable("custom_rule", ruleInput.trim());
            setRuleInput("");
          }
        }}
      />
      <button
        onClick={() => {
          if (ruleInput.trim()) {
            api.setVariable("custom_rule", ruleInput.trim());
            setRuleInput("");
          }
        }}
        style={{
          padding: "6px 14px", background: "#4338ca", borderRadius: "6px",
          color: "#e0e7ff", fontSize: "13px", fontWeight: "600",
          cursor: "pointer", border: "none",
        }}
      >
        Apply
      </button>
    </div>
  </div>
)}

逐行说明:

  • isLastMsg — 只在最后一条消息上显示输入框,否则每条消息都会有一个
  • currentRule — 读取变量的当前值,显示在输入框上方,让玩家看到当前规则
  • ruleInput — React state,追踪正在输入的内容
  • onKeyDown — 按 Enter 也能提交,不只是点击按钮
  • api.setVariable("custom_rule", ...) — 将玩家输入的文本写入变量。下一次 AI 回复时,条目中的 {{custom_rule}} 就会被替换为这段文本
  • setRuleInput("") — 提交后清空输入框

为什么放在 renderBubble 里?

Yumina 的 Root Component 是一个 TSX 文件——默认返回 <Chat /> 就能获得平台内置的聊天 UI。要在聊天中插入交互元素(按钮、输入框),有两种方式:1)放在 <Chat renderBubble={...} /> 里,像这里一样,让它们与消息气泡一起渲染;2)将 <Chat /> 和你的浮动组件放在一个共享的 flex 布局中(用于侧边栏)。如果你想要一个完全脱离聊天的全屏 UI(例如纯视觉小说),就跳过 <Chat />——写你自己的布局,需要时直接使用 <MessageList /> + <MessageInput />


第 4 步:保存并测试

  1. 保存世界,开始新会话
  2. 在最后一条消息下方,你会看到「World Rule: (not set)」和一个输入框
  3. 输入「Magic is forbidden」并点击「Apply」(或按 Enter)
  4. 输入框上方的文字变为「World Rule: Magic is forbidden」——变量已更新
  5. 现在发送一条消息(例如「I try to cast a fireball」)——这时引擎会构建提示词,将 {{custom_rule}} 替换为「Magic is forbidden」
  6. AI 的回复应该反映这个规则(例如「You raise your hand to cast, but your mana feels locked away by some unseen force」)
  7. 再次更改规则(例如改为「Only fire magic is allowed」)并发送另一条消息——AI 会适应新规则

组合两种模式

你可以将问候语切换和条目修改结合起来。一个具体的例子:

角色创建 + 故事开场:

  • 主问候语(index 0) 不是故事——它是一个角色创建表单,包含姓名、职业和背景故事的输入框
  • 玩家填写后 → setVariable 将输入写入变量 → 包含 {{player_name}}{{player_class}}{{player_backstory}} 宏的条目会获取这些值
  • 玩家点击「开始冒险」→ switchGreeting(1) 跳转到真正的故事开场
  • 从第一个 AI 回复开始,AI 就已经知道了玩家角色的姓名、职业和背景故事

快速参考

你想做什么怎么做
跳转到预写的开场switchGreeting(index) — index 对应第一条消息标签页中的问候语顺序(从 0 开始)
让玩家输入改变 AI 行为String 变量 + 条目中的 {{variableId}} + 从 UI 调用 setVariable()
只在第一条消息上显示按钮<Chat renderBubble> 中,检查 msg.messageIndex === 0
选择后隐藏按钮在变量中追踪选择,在 TSX 中检查 hasChosen
选择路线后切换知识创建包含「启用条目」/「禁用条目」动作的行为
切换时播放声音在行为中添加「播放音乐」或「播放音效」动作
切换时显示通知在行为中添加「显示通知」动作

试试看——可导入的演示世界

下载这个 JSON 文件并导入,体验完整效果:

recipe-1-demo.json

如何导入:

  1. 前往 Yumina → 我的世界 → 创建新世界
  2. 在编辑器中,点击「更多操作」→「导入包」
  3. 选择下载的 .json 文件
  4. 世界会被创建,所有问候语、变量、行为和 Root Component 都已预配置
  5. 开始新会话并试试看

包含内容:

  • 3 个问候语(主开场 + 黑暗洞穴 + 阳光草原)
  • 2 个变量(current_route 用于路线追踪,custom_rule 用于玩家可编辑的规则)
  • 2 个动作行为(选择路线时切换知识条目)
  • 一个 Root Component(<Chat renderBubble> 包含路线选择按钮 + 规则编辑器)
  • 一个使用 {{custom_rule}} 宏的知识条目

这是教程 #1

更多教程即将推出——战斗系统、商店界面、任务追踪等等。每个教程都将变量、条目、行为和 UI 组合在一起,构建出大于各部分之和的完整体验。