场景跳转与设定切换
点一个按钮 → 跳到另一段写好的开场白。在输入框里打字 → 改掉一条设定对 AI 说的话。两种做法这里都会讲,先讲不写代码的,再讲界面代码的。
第一部分:用按钮切换开场白
你要做的东西
一张有好几段写好的开场白的卡。玩家先看到「主开场」,下面有几个按钮。点其中一个,聊天里的第一条消息马上换成另一段写好的开场白。不用 AI 生成,就是你写好的那段字。
怎么运作
一张卡可以有好几个开场白。玩家开新的一局时,所有开场白都会打包成第一条消息上的 滑动切换(左右切换)。每个开场白还带着自己的一套变量初始值。
api.switchGreeting(index) 直接跳到对应编号的开场白;玩家界面里按钮的 切换开场白 这一步做的是同一件事,不用写代码。
玩家点了「进入黑暗洞穴」
→ 按钮切换到第 2 个开场白(index 从 0 开始数,所以 1 = 第二个)
→ 变量重置成这个开场白的初始值
→ 玩家看到你写好的黑暗洞穴开场不写代码的做法
- 写好开场白(下面第 1 步)
- 在玩家界面里点 模板,加上 选开局:每个开场白一张卡,玩家点哪张,故事就从哪张开始。也可以自己在页面上放按钮,每个按钮加一步 切换开场白
- 想让每条路线有自己的设定,照下面第 2、3 步做,这两步也不用写代码
选完路线还想做点别的(放音乐、弹提示),就在 切换开场白 这一步 后面 加一步 让一条行为生效。切换会把变量重置成那个开场白的初始值,所以放在切换前面的改动都会丢。
一步一步来
第 1 步:写开场白
在画布上点开场白那一行,写第一段。要再加一段,点开场白块右上角的 +。
第一个开场白(主开场,让玩家选路线):
这是玩家打开这一局最先看到的,描述场景,引导玩家做选择:
*你在一片神秘的森林里醒来。晨雾在古老的树木之间缭绕。*
两条路在你面前分开:
**左边**:一条通往黑暗的窄道。冷风,还有远处的回声。
**右边**:一条洒满阳光的小路,野花遍地,鸟叫个不停。
你走哪条?开场白是 你写好的固定文字,不是 AI 生成的,玩家看到的每个字都由你决定。
第二个开场白(黑暗洞穴):
*你踏上左边的小路。头顶的树冠越来越密,把光都吞掉了。走了几分钟,小路收窄成岩壁上的一道裂缝,是一个洞口。*
*冷风从里面涌出来,带着潮湿石头和一股金属的味道。深处有微弱的蓝绿色光在闪,是长在洞壁上的发光真菌。*
*你吸了口气,走了进去。身后最后一点天光缩成一条白线,然后不见了。*
黑暗里只剩你一个人。这段只有在玩家点了「进入黑暗洞穴」之后才会出现。在那之前,玩家看到的是第一个开场白。
第三个开场白(阳光草原):
*你选了右边的路。树木渐渐稀疏,暖暖的阳光从树冠间洒下来。走了几分钟,森林豁然开朗,一片草原一直铺到天边。*
*各种颜色的野花在微风里轻轻摇。远处一条小溪闪着光。附近有只鸟在唱一支你从没听过的调子。*
*你肩膀上的紧绷一下子松了下来。不管这是什么地方,它让人觉得安全。*
欢迎来到永花草原。开场白的顺序就是 index
开场白的顺序就是 switchGreeting() 的 index,从 0 开始数:第一个开场白是 index 0(默认显示),第二个是 index 1,第三个是 index 2。玩家界面里 切换开场白 这一步把它们列成开局 1、2、3。
第 2 步:建一个路线变量,每个开场白给它一个值
用一个变量记下玩家走的是哪条路线。它有两个用处:
- 选好路线后把按钮藏起来(代码检查它还是不是
"none") - 给每条路线发它自己的设定(第 3 步让设定按它来发)
在画布上点 还可以加 里的 + 变量:
| 字段 | 填什么 | 为什么 |
|---|---|---|
| 变量名称 | current_route | 代码和条件都是按这个名字找变量 |
| 类型 | 文字 | 值是文字("none"、"dark"、"light") |
| 初始值 | none | 意思是「还没选」 |
| AI 权限(在 AI 怎么用它 里) | AI 只读 | 只由开场白来设;AI 看得到,但改不了 |
再给每条路线一个自己的初始值:把第二个开场白拖到 current_route 上,值设成 dark,再把第三个开场白拖上去,值设成 light。玩家切换到哪个开场白,current_route 就变成那个开场白给的值。
第 3 步:(可选)给每条路线自己的设定
想让选完路线以后 AI 的回复用不同的世界设定,就做这一步。只想换开场文字的话,跳过就行。
加两条设定,都做成 条件设定:把 current_route 拖到设定上,再点那根线设条件(洞穴那条是 current_route 等于 dark,草原那条是 light)。条件设定只在条件成立的时候发给 AI。
黑暗洞穴的设定(条件:current_route 等于 dark):
[世界设定:影噬洞穴]
玩家正在探索影噬洞穴。要点:
- 古老的矮人遗迹,已经荒废了几百年
- 发光真菌发出微弱的蓝绿色光
- 更深的隧道里潜伏着奇怪的生物
- 越往里走温度越低
保持紧张的恐怖求生氛围。描写回荡的声音、晃动的影子、滴水声,还有头顶岩石沉沉压下来的感觉。阳光草原的设定(条件:current_route 等于 light):写草原的环境和氛围。
玩家选之前,current_route 是 none,两条设定都不会发。
如果一条路线有很多自己的设定、变量和 AI,就把它放进一个情境,再把开场白拖到这个情境上:这样只有从那个开场白开始的那一局才有这个情境。
第 4 步:用界面代码加路线按钮
想把按钮放进第一条聊天气泡里,就用界面代码写。打开 面板 → 前端代码 → 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.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",
}}
>
进入黑暗洞穴
</button>
<button
onClick={() => 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",
}}
>
走向阳光草原
</button>
</div>
)}
</div>
)} />
);
}逐行说明:
<Chat renderBubble={...} />:用平台默认的聊天界面(输入框、滑动切换、存档点都是自带的),你只接管气泡怎么画const api = useYumina():拿到 Yumina 的 API,可以读变量、写变量、让行为生效、切换开场白api.variables.current_route:读当前路线变量的值hasChosen:不是"none"就说明玩家已经选过了msg.contentHtml:renderBubble 传进来的、已经渲染好的 HTML(Markdown 已经处理过)msg.messageIndex === 0:只在第一条消息上显示按钮,不是每条都显示api.switchGreeting(1):把第一条消息切换到 index 1(第二个开场白)。变量会重置成那个开场白的初始值,所以current_route变成"dark",按钮消失,洞穴设定开始发给 AI
点一下想多做几件事。 如果按钮还要跑一条行为(放音乐、弹提示、改别的数值),就先切换,等切换完成再跑行为:
onClick={async () => {
await api.switchGreeting(1); // 把变量重置成开场白 2 的值
await api.executeActionAndWait("choose-dark"); // 一条行为,触发是「玩家点了某个按钮」→ 按钮 "choose-dark"
}}在切换 之前 调 setVariable 或者跑行为是留不住的:切换会把那个开场白的初始值放回去。
不想自己写代码?
点画布右上角的 创作助手,说说你想要什么样的按钮,它会替你写代码。
第 5 步:保存并测试
- 点顶栏的 保存
- 点 试玩 开始试玩
- 你会看到主开场,下面有两个按钮
- 点「进入黑暗洞穴」,第一条消息变成你写的洞穴开场,按钮消失
- 给 AI 发几条消息。如果做了第 3 步,AI 的回复会照着洞穴的设定来
- 想试另一条路线,重新开一次试玩,点另一个按钮
出问题时:
| 现象 | 可能的原因 | 怎么改 |
|---|---|---|
| 看不到按钮 | 界面代码没保存,或者有语法错误 | 面板 → 前端代码 应该显示 正常,不是 错误 |
| 按钮点了,开场白没换 | 开场白不够 | 确认这张卡有 3 个开场白 |
| 开场白换了,按钮还在 | 这个开场白没给 current_route 设初始值 | 把这个开场白拖到 current_route 上,设好它的值 |
| 设定没切换 | 设定的条件对不上 | 点 current_route 和设定之间的那根线,检查值(dark / light) |
| 点按钮时设的值不见了 | 它是在切换之前设的 | 先 await switchGreeting,再改值 |
第二部分:玩家输入改写设定内容
你要做的东西
一个输入框,玩家在里面打字(一条自定义规则、一个角色名、一句剧情指示)。打的字存进一个变量,一条设定里引用了这个变量,于是 AI 下一次读到的内容就变了。
怎么运作
设定支持 宏。在设定内容里写 {{变量名}},引擎每次给 AI 拼提示词时,都会把它换成这个变量当前的值。
比如:
- 你在设定里写:
特殊规则:{{custom_rule}} - 变量
custom_rule的值是"所有魔法都可以用" - AI 收到的提示词里,这一行就变成:
特殊规则:所有魔法都可以用
替换发生在每次拼提示词的时候,也就是玩家发下一条消息、AI 准备回复的时候。
完整的顺序:
1. 设定内容写着:"特殊规则:{{custom_rule}}"
2. 变量 custom_rule 现在的值 = "所有魔法都可以用"
3. 玩家发消息 → 引擎拼提示词 → 把 {{custom_rule}} 换成变量的值
→ AI 收到 "特殊规则:所有魔法都可以用" → AI 照着回复
4. 玩家在输入框里打「禁止使用魔法」,点「应用」
5. 变量 custom_rule 被设成 "禁止使用魔法"
→ 变量的值马上更新
6. AI 这时还看不到:提示词还没重新拼。
7. 玩家再发一条消息 → 引擎重新拼提示词 → 这次用的是新值
→ AI 收到 "特殊规则:禁止使用魔法" → AI 开始遵守新规则变量是马上变的,AI 要到下一条消息才看到变化。
不写代码的做法
在玩家界面里加一个 填写项 部件(一行字 或 一段话),写进哪个变量 选 custom_rule。玩家一边打字,变量一边更新。再放一个 文字 部件,用 插入变量的值… 显示当前的规则。然后做下面的第 1、2 步;第 3 步只在想把输入框放进聊天里时才需要。
一步一步来
第 1 步:建一个文字变量
这个变量存玩家打的字。在画布上点 还可以加 里的 + 变量:
| 字段 | 填什么 | 为什么 |
|---|---|---|
| 变量名称 | custom_rule | 设定里的 {{custom_rule}} 宏就是按这个名字找的 |
| 类型 | 文字 | 内容是玩家随便打的字 |
| 初始值 | (留空,或者填一个默认值,比如 所有魔法都可以用) | 空 = 新的一局没有规则;不空 = 一开始就有一条规则 |
| AI 权限(在 AI 怎么用它 里) | AI 只读 | 由玩家来设;AI 读得到,但改不了 |
第 2 步:在设定里用宏
建一条用 {{custom_rule}} 占位的设定。在画布上的 角色和世界 块里(每回合都发),点块右上角的 +:
| 字段 | 填什么 | 为什么 |
|---|---|---|
| 名称 | 世界规则 | 给你自己看的 |
| 什么时候发给 AI | 角色和世界(每回合) | 规则对每一条回复都有效 |
内容:
[世界规则]
这个世界里正在生效的规则如下,任何时候都必须遵守:
{{custom_rule}}引擎每次拼提示词时,都会在设定里找
{{...}}。花括号里的名字和某个变量对得上,就换成这个变量现在的值。变量是空的,那一行就是空的,AI 看到「正在生效的规则如下……」后面什么都没有。值是「禁止使用魔法」的话,AI 看到的就是「正在生效的规则如下……禁止使用魔法」。
第 3 步:用界面代码加输入框
这一步在聊天里最后一条消息下面放一个输入框,写在 renderBubble 里面(只放在最后一条消息下面,这样只有一个输入框,不会每条消息下面都有一个)。
在你的 index.tsx 里加上下面这些。如果已经有第一部分的代码,就加在 renderBubble 返回的 JSX 里、消息文字的下面:
// 在 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" }}>
世界规则:{currentRule || "(还没设)"}
</div>
<div style={{ display: "flex", gap: "8px" }}>
<input
type="text"
value={ruleInput}
onChange={(e) => setRuleInput(e.target.value)}
placeholder="输入一条新规则……"
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",
}}
>
应用
</button>
</div>
</div>
)}逐行说明:
isLastMsg:只在最后一条消息上显示输入框,不然每条消息都会有一个currentRule:读变量现在的值,显示在输入框上面,让玩家知道当前规则是什么ruleInput:React state,记着正在打的字onKeyDown:按回车也能提交,不只是点按钮api.setVariable("custom_rule", ...):把玩家打的字写进变量。AI 下一次回复时,设定里的{{custom_rule}}就会换成这段字setRuleInput(""):提交后清空输入框
为什么放在 renderBubble 里?
index.tsx 返回 <Chat /> 就是平台自带的聊天。想往聊天里放能点的东西(按钮、输入框),有两种办法:1)像这里一样放进 <Chat renderBubble={...} />,跟着消息气泡一起画出来;2)用 flex 布局把 <Chat /> 和你自己的组件并排放(做侧边栏用)。如果要一个完全不带聊天的全屏界面(比如视觉小说),就不用 <Chat />,自己写布局,需要的话用 <MessageList /> + <MessageInput />。
第 4 步:保存并测试
- 保存这张卡,点 试玩 开始试玩
- 最后一条消息下面会出现「世界规则:(还没设)」和一个输入框
- 打「禁止使用魔法」,点「应用」(或者按回车)
- 输入框上面的字变成「世界规则:禁止使用魔法」,说明变量已经更新了
- 现在发一条消息(比如「我试着放一个火球」)。这时引擎拼提示词,把
{{custom_rule}}换成「禁止使用魔法」 - AI 的回复应该会体现这条规则(比如「你抬手准备施法,却发现魔力像被什么看不见的东西锁住了」)
- 再改一次规则(比如改成「只能用火系魔法」),再发一条消息,AI 会跟着变
两种做法合起来用
切换开场白和改写设定可以合起来用。举个具体的例子:
建角色 + 故事开场:
- 第一个开场白(index 0) 还不是故事,是一个建角色的页面,有名字、职业、背景故事几个输入框
- 玩家填好 → 输入框写进变量 → 带
{{player_name}}、{{player_class}}、{{player_backstory}}宏的设定拿到这些值 - 玩家点「开始冒险」→
switchGreeting(1)跳到真正的故事开场。切换会把变量重置成那个开场白的初始值,所以要在切换 之后 再存玩家填的内容(先 awaitswitchGreeting,再setVariable或patchVariables) - 从 AI 的第一条回复开始,它就已经知道玩家角色的名字、职业和背景故事
玩家界面里的开局模板(填名字进故事、建档、选出身、分配属性点)不写代码就能在故事开始前做出这种页面。
速查
| 你想要 | 怎么做 |
|---|---|
| 跳到一段写好的开场白 | 玩家界面:切换开场白 这一步,或者 选开局 模板。代码:await api.switchGreeting(index)(从 0 开始,按开场白的顺序) |
| 每个开场白不同的数值 | 在画布上把开场白拖到变量上,设好初始值 |
| 让玩家的输入改变 AI 的表现 | 文字变量 + 设定里的 {{变量名}} + 一个 填写项(或者代码里调 setVariable()) |
| 按钮只在第一条消息上显示 | 在 <Chat renderBubble> 里判断 msg.messageIndex === 0 |
| 选完以后藏起按钮 | 用一个变量记下选择,在 TSX 里判断 hasChosen |
| 选完路线切换设定 | 按路线变量设的 条件设定,或者绑在开场白上的情境 |
| 切换时放声音或弹提示 | 一条带 播放音乐 / 播放音效 / 显示通知 的行为,在切换之后让它生效 |
自己试试:可导入的演示
下载这个 JSON 文件导入进去:
怎么导入:
- 点左边的 创建,选 空白项目
- 打开 面板 → 卡的设置,点 导入文件 → 选择文件
- 选下载好的
.json文件。导入会替换掉这张卡里的所有内容 - 点 保存,再点 试玩 试试
里面有:
- 3 个开场白(主开场 + 黑暗洞穴 + 阳光草原)
- 2 个变量(
current_route记路线,custom_rule存玩家可以改的规则) - 2 条触发是 玩家点了某个按钮 的行为(
choose-dark/choose-light),负责设路线、开关路线设定 - 一个消息渲染器(画每条消息的界面代码),带路线按钮和规则编辑框
- 一条用了
{{custom_rule}}宏的设定
演示里的按钮是一下子做完三件事:设路线、跑行为、再切换开场白。切换会把变量重置成开场白的初始值,所以路线可能留不住;上面第 2 到 4 步的做法不会有这个问题。
