场景跳转与条目切换
点击按钮 → 跳转到另一段预写的开场。在文本框中输入 → 改变条目对 AI 说的内容。本教程将向你展示这两种用法。
第一部分:通过按钮切换不同开场
你将构建什么
一个拥有多个预写开场的世界。玩家首先看到「主开场」,下方有可点击的按钮。当他们点击其中一个时,聊天中的第一条消息会立即切换到另一段预写开场——不需要 AI 生成,只是你写好的文本。
工作原理
在 Yumina 中,你可以在编辑器的第一条消息标签页创建多个问候语。当玩家开始新会话时,所有问候语会被打包为第一条消息上的滑动切换(左右滑动切换)。玩家已经可以手动滑动了——但我们想要的是:让玩家通过一次按钮点击就跳转到特定的问候语。
这就是 switchGreeting(index) API 的用途——它让自定义组件通过代码直接跳转到第 N 个问候语。
玩家点击「进入黑暗洞穴」
→ 代码调用 api.switchGreeting(1)
→ 第一条消息切换到第 2 个问候语(index 从 0 开始,所以 1 = 第二个)
→ 玩家立即看到你预写的黑暗洞穴开场分步教程
第 1 步:在第一条消息标签页创建多个问候语
打开编辑器,点击左侧边栏的第一条消息标签页。
这个标签页专门用于管理开场内容。你可以创建多个问候语——每个都会变成一个滑动页。
创建第一个问候语(主开场——展示路线选择):
点击「创建第一条消息」。在文本框中写下主开场。这是玩家打开会话时首先看到的内容——描述场景并引导他们做出选择:
*你在一片神秘的森林中醒来。晨雾在古老的树木之间缭绕。*
两条道路在你面前分叉:
**左边** — 一条通往黑暗的狭窄小径。冰冷的空气和远处的回声。
**右边** — 一条洒满阳光的小路,野花遍地,鸟鸣声声。
你会走哪条路?为什么只描述场景而不让 AI 回复?因为问候语是你预先写好的固定文本,不是 AI 生成的。你可以精确控制玩家看到的每一个字。
创建第二个问候语(黑暗洞穴开场):
点击底部的「添加问候语」。你会看到编号标签 1 和 2 出现。点击 2 切换到第二个问候语的编辑框。写下黑暗洞穴路线的开场:
*你踏上左边的小路。头顶的树冠越来越密,吞噬了光线。几分钟后,小径缩窄成岩壁上的一道裂缝——一个洞穴的入口。*
*冰冷的空气涌出,带着潮湿石头和某种金属味的气息。微弱的蓝绿色光在深处闪烁——生物发光的真菌攀附在洞壁上。*
*你深吸一口气走了进去。身后,最后一缕阳光缩成一条苍白的线,然后消失了。*
你孤身一人在黑暗中。这段文字只有在玩家点击「进入黑暗洞穴」后才会显示。在此之前,玩家看到的是第一个问候语(主开场)。
创建第三个问候语(阳光草原开场):
再次点击「添加问候语」。切换到标签 3,写下阳光草原路线的开场:
*你选择了右边的小路。树木渐渐稀疏,温暖的阳光透过树冠倾泻而下。几分钟后,森林开阔成一片延伸到地平线的广袤草原。*
*各种颜色的野花在微风中轻轻摇曳。远处一条小溪闪闪发光。附近某处,一只鸟唱着你从未听过的旋律。*
*你感觉肩膀上的紧绷感融化了。不管这个地方是什么,它让人感到安全。*
欢迎来到永花草原。问候语的顺序就是 index
底部编号标签的顺序就是 switchGreeting() 的 index 参数。标签 1 = index 0(默认显示),标签 2 = index 1,标签 3 = index 2。你在写按钮代码时会用到这个 index。
现在你有了 3 个问候语。保存世界后,新会话默认显示第一个(主开场)。接下来我们要做的是创建按钮,让玩家可以点击跳转到第二个或第三个。
第 2 步:创建一个路线追踪变量
我们需要一个变量来记录「玩家选择了哪条路线」。这个变量有两个用途:
- 选择后让按钮消失(TSX 代码检查这个变量——如果不是
"none",就不显示按钮) - 让后续对话知道当前路线(行为规则可以根据这个变量切换知识条目)
编辑器 → 左侧边栏 → 变量标签页 → 点击「添加变量」
| 字段 | 填写内容 | 原因 |
|---|---|---|
| 显示名称 | Current Route | 供你自己参考 |
| ID | current_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 → 粘贴以下代码(替换默认代码):
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 步:保存并测试
- 点击编辑器顶部的「保存」
- 点击「开始游戏」或返回主页开始新会话
- 你会看到主开场,下方有两个按钮
- 点击「Enter the Dark Cave」——第一条消息立即变为你预写的洞穴开场,按钮消失
- 向 AI 发送几条消息——如果你完成了第 3 步,AI 的回复会受到洞穴知识的影响
- 想测试另一条路线?返回主页开始新会话,这次点击另一个按钮
故障排除:
| 症状 | 可能原因 | 解决方法 |
|---|---|---|
| 看不到按钮 | 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 | 供你自己参考 |
| ID | custom_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 返回的内容中,消息文本下方添加:
// 在 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 步:保存并测试
- 保存世界,开始新会话
- 在最后一条消息下方,你会看到「World Rule: (not set)」和一个输入框
- 输入「Magic is forbidden」并点击「Apply」(或按 Enter)
- 输入框上方的文字变为「World Rule: Magic is forbidden」——变量已更新
- 现在发送一条消息(例如「I try to cast a fireball」)——这时引擎会构建提示词,将
{{custom_rule}}替换为「Magic is forbidden」 - AI 的回复应该反映这个规则(例如「You raise your hand to cast, but your mana feels locked away by some unseen force」)
- 再次更改规则(例如改为「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 文件并导入,体验完整效果:
如何导入:
- 前往 Yumina → 我的世界 → 创建新世界
- 在编辑器中,点击「更多操作」→「导入包」
- 选择下载的
.json文件 - 世界会被创建,所有问候语、变量、行为和 Root Component 都已预配置
- 开始新会话并试试看
包含内容:
- 3 个问候语(主开场 + 黑暗洞穴 + 阳光草原)
- 2 个变量(
current_route用于路线追踪,custom_rule用于玩家可编辑的规则) - 2 个动作行为(选择路线时切换知识条目)
- 一个 Root Component(
<Chat renderBubble>包含路线选择按钮 + 规则编辑器) - 一个使用
{{custom_rule}}宏的知识条目
这是教程 #1
更多教程即将推出——战斗系统、商店界面、任务追踪等等。每个教程都将变量、条目、行为和 UI 组合在一起,构建出大于各部分之和的完整体验。
