Skip to content

场景跳转与设定切换 ​

点一个按钮 → 跳到另一段写好的开场白。在输入框里打字 → 改掉一条设定对 AI 说的话。两种做法这里都会讲,先讲不写代码的,再讲界面代码的。


第一部分:用按钮切换开场白 ​

你要做的东西 ​

一张有好几段写好的开场白的卡。玩家先看到「主开场」,下面有几个按钮。点其中一个,聊天里的第一条消息马上换成另一段写好的开场白。不用 AI 生成,就是你写好的那段字。

怎么运作 ​

一张卡可以有好几个开场白。玩家开新的一局时,所有开场白都会打包成第一条消息上的 滑动切换(左右切换)。每个开场白还带着自己的一套变量初始值。

api.switchGreeting(index) 直接跳到对应编号的开场白;玩家界面里按钮的 切换开场白 这一步做的是同一件事,不用写代码。

玩家点了「进入黑暗洞穴」
  → 按钮切换到第 2 个开场白(index 从 0 开始数,所以 1 = 第二个)
  → 变量重置成这个开场白的初始值
  → 玩家看到你写好的黑暗洞穴开场

不写代码的做法 ​

  1. 写好开场白(下面第 1 步)
  2. 在玩家界面里点 模板,加上 选开局:每个开场白一张卡,玩家点哪张,故事就从哪张开始。也可以自己在页面上放按钮,每个按钮加一步 切换开场白
  3. 想让每条路线有自己的设定,照下面第 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,把里面的内容换成:

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

点一下想多做几件事。 如果按钮还要跑一条行为(放音乐、弹提示、改别的数值),就先切换,等切换完成再跑行为:

tsx
onClick={async () => {
  await api.switchGreeting(1);                 // 把变量重置成开场白 2 的值
  await api.executeActionAndWait("choose-dark"); // 一条行为,触发是「玩家点了某个按钮」→ 按钮 "choose-dark"
}}

在切换 之前 调 setVariable 或者跑行为是留不住的:切换会把那个开场白的初始值放回去。

不想自己写代码?

点画布右上角的 创作助手,说说你想要什么样的按钮,它会替你写代码。


第 5 步:保存并测试 ​

  1. 点顶栏的 保存
  2. 点 试玩 开始试玩
  3. 你会看到主开场,下面有两个按钮
  4. 点「进入黑暗洞穴」,第一条消息变成你写的洞穴开场,按钮消失
  5. 给 AI 发几条消息。如果做了第 3 步,AI 的回复会照着洞穴的设定来
  6. 想试另一条路线,重新开一次试玩,点另一个按钮

出问题时:

现象可能的原因怎么改
看不到按钮界面代码没保存,或者有语法错误面板 → 前端代码 应该显示 正常,不是 错误
按钮点了,开场白没换开场白不够确认这张卡有 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 里、消息文字的下面:

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" }}>
      世界规则:{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 步:保存并测试 ​

  1. 保存这张卡,点 试玩 开始试玩
  2. 最后一条消息下面会出现「世界规则:(还没设)」和一个输入框
  3. 打「禁止使用魔法」,点「应用」(或者按回车)
  4. 输入框上面的字变成「世界规则:禁止使用魔法」,说明变量已经更新了
  5. 现在发一条消息(比如「我试着放一个火球」)。这时引擎拼提示词,把 {{custom_rule}} 换成「禁止使用魔法」
  6. AI 的回复应该会体现这条规则(比如「你抬手准备施法,却发现魔力像被什么看不见的东西锁住了」)
  7. 再改一次规则(比如改成「只能用火系魔法」),再发一条消息,AI 会跟着变

两种做法合起来用 ​

切换开场白和改写设定可以合起来用。举个具体的例子:

建角色 + 故事开场:

  • 第一个开场白(index 0) 还不是故事,是一个建角色的页面,有名字、职业、背景故事几个输入框
  • 玩家填好 → 输入框写进变量 → 带 {{player_name}}、{{player_class}}、{{player_backstory}} 宏的设定拿到这些值
  • 玩家点「开始冒险」→ switchGreeting(1) 跳到真正的故事开场。切换会把变量重置成那个开场白的初始值,所以要在切换 之后 再存玩家填的内容(先 await switchGreeting,再 setVariable 或 patchVariables)
  • 从 AI 的第一条回复开始,它就已经知道玩家角色的名字、职业和背景故事

玩家界面里的开局模板(填名字进故事、建档、选出身、分配属性点)不写代码就能在故事开始前做出这种页面。


速查 ​

你想要怎么做
跳到一段写好的开场白玩家界面:切换开场白 这一步,或者 选开局 模板。代码:await api.switchGreeting(index)(从 0 开始,按开场白的顺序)
每个开场白不同的数值在画布上把开场白拖到变量上,设好初始值
让玩家的输入改变 AI 的表现文字变量 + 设定里的 {{变量名}} + 一个 填写项(或者代码里调 setVariable())
按钮只在第一条消息上显示在 <Chat renderBubble> 里判断 msg.messageIndex === 0
选完以后藏起按钮用一个变量记下选择,在 TSX 里判断 hasChosen
选完路线切换设定按路线变量设的 条件设定,或者绑在开场白上的情境
切换时放声音或弹提示一条带 播放音乐 / 播放音效 / 显示通知 的行为,在切换之后让它生效

自己试试:可导入的演示 ​

下载这个 JSON 文件导入进去:

recipe-1-demo-zh.json

怎么导入:

  1. 点左边的 创建,选 空白项目
  2. 打开 面板 → 卡的设置,点 导入文件 → 选择文件
  3. 选下载好的 .json 文件。导入会替换掉这张卡里的所有内容
  4. 点 保存,再点 试玩 试试

里面有:

  • 3 个开场白(主开场 + 黑暗洞穴 + 阳光草原)
  • 2 个变量(current_route 记路线,custom_rule 存玩家可以改的规则)
  • 2 条触发是 玩家点了某个按钮 的行为(choose-dark / choose-light),负责设路线、开关路线设定
  • 一个消息渲染器(画每条消息的界面代码),带路线按钮和规则编辑框
  • 一条用了 {{custom_rule}} 宏的设定

演示里的按钮是一下子做完三件事:设路线、跑行为、再切换开场白。切换会把变量重置成开场白的初始值,所以路线可能留不住;上面第 2 到 4 步的做法不会有这个问题。