Skip to content

商店与交易 ​

一个玩家能买东西的商店:玩家看得到自己的金币和在卖什么,点「购买」,金币扣掉,再告诉他买到了什么。金币不够的话,就告诉他不够。整个商店只要一条行为,每个按钮只负责告诉它买的是哪件、多少钱。


你要做的东西 ​

  • 金币显示在画面上
  • 商店页,每件商品一个购买按钮:药水(20 金币)、铁剑(50 金币)
  • 买东西:金币按价格扣掉,弹个提示说买了什么,同时告诉 AI
  • 金币不够:「金币不够,铁剑要 50。」什么都不扣
  • 买到的东西进玩家的背包

怎么运作 ​

玩家点了「买铁剑」
  → 按钮让行为「购买」生效,传进去 item = 铁剑,price = 50
  → 行为检查:Gold ≥ {param.price}?
    → 是:Gold 减去 {param.price},提示「你买了铁剑」,同时告诉 AI
    → 否:弹出「条件不满足时告诉玩家」里写的那句话,别的什么都不发生

{param.price} 和 {param.item} 代表按钮传进来的值。所以不管有多少件商品,一条行为就够了。


一步一步来 ​

第 1 步:建变量 ​

点卡底下 还可以加 里的 + 变量。

Gold ​

字段值
名称Gold
类型数值
初始值100
范围0 到(不填)
行为规则玩家的金币。商店会自动扣掉价格。剧情里你也可以改它:任务奖励、被抢、宝箱。

最小值 0 是一道保险:行为本来就会检查玩家买不买得起,但设了范围以后,金币无论如何都不会显示成负数。

点 放到玩家界面上,让金币一直显示在聊天里。

背包 ​

最省事的背包来自 玩家界面 → 模板 → 背包:一个背包页、拿到新东西时的弹窗,还有一个 背包 列表变量,它的行为规则会告诉 AI 剧情里给了玩家什么就往里加。见背包与装备这篇食谱。


第 2 步:建一条「购买」行为 ​

点 还可以加 里的 + 行为。

部分设置
什么时候触发玩家点了某个按钮,按钮 buy
如果……Gold ≥ {param.price}
效果修改变量:Gold 减去 {param.price}
显示通知:你买了:{param.item},样式选 成功
告诉 AI:玩家刚花 {param.price} 金币买了 {param.item}。把它加进背包。
条件不满足时告诉玩家金币不够,{param.item} 要 {param.price}。

不写最后这一行的话,买不起的玩家点了按钮,什么反应都没有。

告诉 AI 会给 AI 塞一句话,只管下一次回复。配上背包模板的行为规则,AI 会把东西放进背包,并弹出「得到了」的提示。

让东西稳稳地进背包

行为的效果能改数值和文字,但没法往列表里加一行。这里是 AI 在 告诉 AI 的提醒下把东西加进去的。如果需要分毫不差:

  • 能数的东西用数值记:给药水单独建一个数值变量,再单独做一条「买药水」行为,效果里多一个 修改变量 Potions 加上 1
  • 代码效果:在行为的效果里加一段 代码,写一行 ctx.push("背包", { name: "药水", note: "能回一点血。" })。见行为 · 代码行为
  • 界面代码:下面写代码的做法会自己写这个列表

第 3 步:做商店页 ​

点顶栏中间的 玩家界面。用 新建一页 加一页(选 空白页),起名叫「商店」。

  1. 加一个 文字 部件,写 金币:{Gold}(用 插入变量的值…)
  2. 每件商品加一个 文字 部件写名字和介绍,再加一个写着 购买 · 20 金币 的 按钮
  3. 在按钮的 按下时,依次做 里加 让一条行为生效 → 购买,然后点两次 + 传一个值进去:item = 药水,price = 20。铁剑就是 铁剑 和 50
  4. 加一个按钮,步骤选 跳到页面,跳回对话页;再在对话页上加一个「商店」按钮跳到这一页

想把买不起的商品显示出来,就把购买按钮复制一个,把复制的那个弄成灰色的样子,再用 什么时候显示 → 变量满足条件时:灰色的那个填 Gold 小于 20,正常的那个填 Gold 大于等于 20。灰色的按钮照样能让行为生效,所以点它会弹出「金币不够」。

把 编辑 关掉就能点着试试这一页。预览不会调用 AI,但能看到金币在变。

会变的价格

价格可以来自一个变量:在 + 传一个值进去 里写 {{Potion price}},不写 20。改了这个变量(比如过节时用一条行为改),所有按钮都跟着变。


第 4 步:试玩 ​

点顶栏的 试玩。

  1. 金币显示 100。打开 商店
  2. 买一瓶药水:金币 80,「你买了:药水」。发一条消息,AI 会提到这件事,药水出现在背包里
  3. 再买一瓶:金币 60
  4. 买铁剑:金币 10
  5. 随便再买点什么:「金币不够,药水要 20。」金币还是 10

试玩右边的 这回合 能看到购买行为有没有触发,没触发的话是为什么。

如果出了问题:

现象可能的原因怎么改
点购买没反应按钮的 让一条行为生效 选的是别的行为,或者这条行为没 启用两个都检查一下
金币没变传进去的值的名字和行为里的 {param.…} 不一样按钮上是 price,行为里是 {param.price},拼写要一样
金币不够时没有提示条件不满足时告诉玩家 没写把那句话写上
金币变成负数少了条件,或者 Gold 没设最小值保留 如果……,范围 从 0 开始
背包里一直没出现这件东西AI 没加进去行为里保留 告诉 AI,背包变量的行为规则也留着,或者用上面那几种分毫不差的做法

写代码的做法:聊天下方的商店面板 ​

这个版本用界面代码画商店。它检查金币时还是用 购买 这条行为,所以价格和「金币不够」的提示都留在画布上;买成以后,它自己把东西加进一个 inventory 列表。这里假设你有一个叫 inventory、初始值为 [] 的列表/表格变量。

面板 → 前端代码 → 打开 index.tsx,把默认的 return <Chat /> 换掉:

tsx
export default function MyWorld() {
  const api = useYumina();
  const msgs = api.messages || [];

  const gold = Number(api.variables["Gold"] ?? 0);
  const inventory = Array.isArray(api.variables.inventory) ? api.variables.inventory : [];

  const shopItems = [
    { name: "药水", price: 20, icon: "🧪", desc: "能回一点血" },
    { name: "铁剑", price: 50, icon: "⚔️", desc: "一把普通的铁剑" },
  ];

  const buy = async (item) => {
    // 运行 "buy" 行为并等它跑完;条件不满足时 firedIds 是空的
    // (行为自己会弹出「金币不够」的提示)
    const result = await api.executeActionAndWait("buy", { item: item.name, price: item.price });
    if (result.firedIds.length > 0) {
      const current = Array.isArray(api.variables.inventory) ? api.variables.inventory : [];
      api.setVariable("inventory", [...current, item.name]);
    }
  };

  return (
    <Chat renderBubble={(msg) => {
      const isLastMsg = msg.messageIndex === msgs.length - 1;
      return (
        <div>
          <div
            style={{ color: "#e2e8f0", lineHeight: 1.7 }}
            dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
          />

          {isLastMsg && (
            <div style={{
              marginTop: "16px",
              padding: "16px",
              background: "rgba(15, 23, 42, 0.6)",
              borderRadius: "12px",
              border: "1px solid #334155",
            }}>
              {/* 金币 */}
              <div style={{
                display: "flex", alignItems: "center", gap: "8px", marginBottom: "16px",
                padding: "10px 14px", background: "linear-gradient(135deg, #78350f, #92400e)",
                borderRadius: "8px", border: "1px solid #b45309",
              }}>
                <span style={{ fontSize: "20px" }}>💰</span>
                <span style={{ color: "#fde68a", fontSize: "16px", fontWeight: "bold" }}>{gold} 金币</span>
              </div>

              {/* 在卖的商品 */}
              <div style={{ display: "flex", flexDirection: "column", gap: "8px", marginBottom: "16px" }}>
                {shopItems.map((item) => {
                  const affordable = gold >= item.price;
                  return (
                    <div key={item.name} style={{
                      display: "flex", alignItems: "center", justifyContent: "space-between",
                      padding: "10px 14px", background: "rgba(30, 41, 59, 0.8)",
                      borderRadius: "8px", border: "1px solid #475569",
                    }}>
                      <div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
                        <span style={{ fontSize: "22px" }}>{item.icon}</span>
                        <div>
                          <div style={{ color: "#e2e8f0", fontSize: "14px", fontWeight: "600" }}>{item.name}</div>
                          <div style={{ color: "#64748b", fontSize: "12px" }}>{item.desc}</div>
                        </div>
                      </div>
                      <button
                        onClick={() => buy(item)}
                        style={{
                          padding: "6px 16px", borderRadius: "6px", fontSize: "13px", fontWeight: "600", whiteSpace: "nowrap",
                          background: affordable ? "#047857" : "#4b5563",
                          border: "1px solid " + (affordable ? "#10b981" : "#6b7280"),
                          color: affordable ? "#a7f3d0" : "#9ca3af",
                          cursor: "pointer",
                          opacity: affordable ? 1 : 0.6,
                        }}
                      >
                        {item.price} 金币
                      </button>
                    </div>
                  );
                })}
              </div>

              {/* 背包 */}
              {inventory.length === 0 ? (
                <div style={{
                  padding: "20px", textAlign: "center", color: "#475569", fontSize: "13px",
                  background: "rgba(30, 41, 59, 0.4)", borderRadius: "8px", border: "1px dashed #334155",
                }}>
                  背包是空的
                </div>
              ) : (
                <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(80px, 1fr))", gap: "8px" }}>
                  {inventory.map((name, idx) => {
                    const def = shopItems.find((s) => s.name === String(name));
                    return (
                      <div key={idx} style={{
                        display: "flex", flexDirection: "column", alignItems: "center", gap: "4px",
                        padding: "10px 6px", background: "rgba(30, 41, 59, 0.8)",
                        borderRadius: "8px", border: "1px solid #475569",
                      }}>
                        <span style={{ fontSize: "24px" }}>{def ? def.icon : "📦"}</span>
                        <span style={{ color: "#cbd5e1", fontSize: "11px", textAlign: "center" }}>{String(name)}</span>
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          )}
        </div>
      );
    }} />
  );
}

代码做了什么:

  • api.executeActionAndWait("buy", { item, price }) 让购买行为生效,传的值和玩家界面上的按钮传的一样,然后等它跑完
  • result.firedIds 列出跑了的行为。空的就是条件没满足:行为已经弹过「金币不够」的提示了,也不会加东西
  • api.setVariable("inventory", [...current, item.name]) 把东西加进列表
  • 买不起的商品是灰的,但还是能点,这样玩家能看到提示
  • repeat(auto-fill, minmax(80px, 1fr)) 按宽度一行能放几格就放几格

如果用这个做法,把行为里 告诉 AI 那句改成 玩家刚花 {param.price} 金币买了 {param.item}。,免得 AI 再加一次。

面板 → 前端代码 里文件旁边的状态应该是绿色的 正常。

不想自己写代码?

点画布右上角的 创作助手,说说你想要什么样的商店。


再往前走一步 ​

更多商品 ​

加一个按钮(或者在 shopItems 里加一行),写上它自己的名字和价格。行为不用改。

卖东西 ​

再建一条行为「出售」,按钮 sell,效果是 Gold 加上 {param.price},告诉 AI 玩家卖掉了 {param.item}。把它从背包里拿掉。

赚金币 ​

  • 每 N 回合(比如 3):修改变量 Gold 加上 10
  • AI 说出关键词 你赢了这场战斗, 奖励归你了:Gold 加上 25(任意一个关键词出现就触发)
  • AI 自己按 Gold 的行为规则来改,剧情里的战利品和奖励都算
  • 任务追踪里的任务奖励

会讲价的店主 ​

想要一个按角色说话、偶尔给个折扣的商人,就加一个界面型的 AI,在 回答格式 里加一个价格字段,再用按钮的 叫一个 AI 来叫它。


速查 ​

你想要怎么做
记金币一个数值变量,最小值 0
一条行为管整个商店玩家点了某个按钮,值用 {param.item} / {param.price} 读
传商品和价格按钮步骤 让一条行为生效 → + 传一个值进去
检查买不买得起如果…… Gold ≥ {param.price}
买不起时告诉玩家条件不满足时告诉玩家
确认买到了显示通知,样式选 成功
把东西放进背包告诉 AI 加背包模板、每件东西一个数值变量、一段 代码 效果,或者界面代码
买不起的显示成灰色按钮做两个版本,用 什么时候显示
从代码里跑同一条行为api.executeActionAndWait("buy", { item, price })

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

下载这个 JSON 导入进去,就能看到能跑的版本:

recipe-3-demo-zh.json

怎么导入:

  1. 点左边的 创建,选 空白项目,再选 画布
  2. 在顶栏点 ⋮ → 导入文件,选下载好的 .json 文件。它会替换掉这张新卡里的所有内容
  3. 点 保存,再点 试玩

演示里每件商品用两条行为(买到了 / 金币不够),商店面板写在界面代码里。


这是食谱 #3

按钮把值传给同一条行为,这个办法适用于任何有价格、从菜单里选的东西:制作、训练、雇人。见行为 · 按钮传值。