Skip to content

背包与装备 ​

一个玩家能打开、能用的背包:故事里拿到的东西会进背包,药水可以喝(生命涨,数量减),武器可以装备上,让 AI 知道玩家手里拿着什么。背包、药水和武器都能不写代码做出来;每件物品自带按钮的完整物品格子,放在最后写代码的做法里。


你要做的东西 ​

  • 一个背包,玩家从聊天页打开:捡到的东西都在里面,每样配一句描述,故事里拿到新东西时会弹窗
  • 一个喝药水的按钮:生命 +20,药水少一瓶,没有了就提示「药水用完了!」
  • 一个装备按钮:武器栏显示「铁剑」,AI 会写玩家握着它
  • AI 跟着剧情更新背包:战利品、奖励、丢了或坏了的东西

各部分谁来管 ​

什么谁来管为什么
故事里捡到的物品列表AI,靠背包变量的行为规则只有 AI 知道故事什么时候把东西交给玩家
数药水、回血行为算数要准
当前装备的武器按钮或行为就一个值,玩家选的时候设好
把这些显示出来玩家界面模板和部件,不用代码

一步一步来 ​

第 1 步:加背包模板 ​

点顶栏中间的 玩家界面,再点 模板,加 背包(在 玩的时候 · 从聊天页的「菜单」打开 里):点物品就是拿出来用;故事里拿到新东西会弹窗。

它会加一个背包页、聊天页上的一个弹窗,还有两个变量:

变量是什么
背包(列表/表格)一件物品一行,每行有名字和一句备注
刚得到的东西(文字)玩家刚拿到的东西;弹窗显示它,关掉时清空

两个变量都自带行为规则,教 AI 怎么用。背包的规则里有这么一段:

玩家拿到东西时 push 一件,同一回合把 new_item 设成「物品名——一句话」;用掉或丢了就 delete 它的下标(从 0 数)。不要每回合都塞东西。

AI 在故事里照着做,所以战利品、礼物、奖励会自己进背包,你什么都不用管。

在背包页上点一件物品,会替玩家说一句「(我拿出旧怀表)」再回到聊天,AI 就会把用它的过程演出来。

开局就有的物品写进背包变量的 初始值,比如:

json
[{"name": "铁剑", "note": "朴素,但手感很稳。"}]

第 2 步:能数清楚的药水 ​

背包是 AI 来写的列表。药水还剩几瓶这种要精确数字的东西,用一个数值变量加一条行为。

变量(还可以加 里的 + 变量):

名称类型初始值范围备注
生命数值800 到 100行为规则:玩家受伤时减少;休息时慢慢恢复。
药水数值20 到(不填)行为规则:玩家身上有几瓶治疗药水。找到或买到时增加。

生命一开始不满,玩家才有理由试试喝药水。

行为(+ 行为):「喝药水」

部分设置
什么时候触发玩家点了某个按钮,按钮 drink-potion
如果……药水 大于 0
效果修改变量:药水 减去 1
修改变量:生命 加上 20
显示通知:你喝下一瓶药水。生命 +20,样式 成功
告诉 AI:玩家刚喝了一瓶治疗药水。
条件不满足时告诉玩家药水用完了!

生命不会超过 100:变量的范围会卡住它。

按钮:在 玩家界面 里,对话页或者背包页上加一个 按钮,文字写「喝药水(还剩 {药水} 瓶)」(数量用 插入变量的值… 插进去)。在 按下时,依次做 里加 让一条行为生效 → 喝药水。

第 3 步:装备武器 ​

变量:当前武器,文字,初始为空。行为规则:

玩家手里拿着的武器;空着就是赤手空拳。打斗时要写到它。如果故事里它坏了或被夺走,就把它设回空。

按钮:「装备铁剑」,两个步骤:

  1. 改变量:当前武器,设为,铁剑
  2. 弹出提示:装备了铁剑

给它设 什么时候显示 → 变量满足条件时:当前武器 不等于 铁剑,装备上以后按钮就消失。旁边放一个 文字 部件写 武器:{当前武器},用来显示武器栏。

AI 每回合的提示里都看得到当前武器,所以它的下一次回复里玩家就握着这把剑。想让它马上就有反应,就让按钮 让一条行为生效,由那条行为去改变量,再加一个 告诉 AI:玩家刚装备了铁剑。

第 4 步:试玩 ​

点顶栏的 试玩。

  1. 生命显示 80,药水 2,武器栏是空的
  2. 点 喝药水:生命 100,药水 1,弹出提示
  3. 再点两次:第二次弹出「药水用完了!」,什么都没变
  4. 点 装备铁剑:武器栏显示铁剑,按钮消失。打一架,AI 会把剑写进去
  5. 一直玩到故事给你一样东西:弹窗显示它,背包里也有了(菜单 → 背包)

如果出了问题:

现象可能的原因怎么改
药水按钮没反应按钮叫的行为和行为里的按钮名对不上让一条行为生效 要指向「喝药水」
药水变成了负数少了 如果…… 条件加上 药水 大于 0
生命超过了 100生命没设上限把它的 范围 设成 0 到 100
东西一直没进背包AI 没在写背包变量检查背包变量的行为规则还在,AI 权限 是 AI 可读可写
弹窗一直不出来AI 改了背包,但没改刚得到的东西保留刚得到的东西的行为规则,它要求同一回合两个都改

为什么这里光靠行为做不完 ​

行为的效果能改数字和文字(设为、加上、减去、乘以、切换、追加文字)。它没法往列表里加一行,没法在列表里找到「药水」那一行,也没法把它删掉。条件能检查列表是不是 包含 某个简单的值,但看不到行里面的内容。所以这个食谱把物品列表交给 AI,要精确数字的东西各自用一个数值变量来数。

行为的 代码 效果可以往列表里加东西,前提是你写一行 JavaScript,比如 ctx.push("背包", { name: "药水", note: "能治一点伤。" })。见行为 · 代码行为。

想两样都要(一个真正的物品格子,每件物品有数量,还有自己的使用或装备按钮),就在界面代码里写。


写代码的做法:能使用、能装备的物品格子 ​

这个版本把整个背包放在一个叫 inventory 的列表/表格变量里,每件物品带数量,使用和装备都在界面代码里处理。它还用到数值变量 hp(0 到 100,初始 80)和文字变量 equipped_weapon。

inventory 的初始值:

json
[{"name":"药水","icon":"🧪","count":2},{"name":"铁剑","icon":"⚔️","count":1}]

它的行为规则:

玩家的物品。每件是 {"name", "icon", "count"}。玩家找到东西时 push 一件新物品。数量交给背包按钮去管。

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

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

  const hp = Number(api.variables.hp ?? 80);
  const equippedWeapon = String(api.variables.equipped_weapon || "");
  const inventory = Array.isArray(api.variables.inventory) ? api.variables.inventory : [];

  // 用掉一件:数量减一,减到零就删掉,再执行它的效果
  function useItem(itemName) {
    const inv = Array.isArray(api.variables.inventory) ? api.variables.inventory : [];
    const idx = inv.findIndex((it) => it && it.name === itemName);
    if (idx === -1) {
      api.showToast(itemName + "用完了!", "error");
      return;
    }
    const item = inv[idx];
    const next = inv.slice();
    if (Number(item.count) <= 1) next.splice(idx, 1);
    else next[idx] = { ...item, count: Number(item.count) - 1 };
    api.setVariable("inventory", next);

    if (itemName === "药水") {
      api.setVariable("hp", Math.min(Number(api.variables.hp ?? 0) + 20, 100));
      api.showToast("喝下一瓶药水!HP +20", "success");
      api.injectContext("玩家刚喝了一瓶治疗药水。");
    }
  }

  function equipItem(itemName) {
    if (equippedWeapon === itemName) {
      api.showToast(itemName + "已经装备上了!", "info");
      return;
    }
    api.setVariable("equipped_weapon", itemName);
    api.showToast("装备了" + itemName + "!", "success");
    api.injectContext("玩家刚装备了" + itemName + "。");
  }

  // 每件物品配什么按钮
  const itemActions = {
    "药水": { type: "consumable", label: "使用", run: () => useItem("药水") },
    "铁剑": { type: "equipment", label: "装备", run: () => equipItem("铁剑") },
  };

  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: "10px", marginBottom: "14px" }}>
                <span style={{ fontSize: "16px" }}>❤️</span>
                <div style={{ flex: 1 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", marginBottom: "4px" }}>
                    <span style={{ color: "#94a3b8", fontSize: "12px" }}>HP</span>
                    <span style={{ color: "#e2e8f0", fontSize: "12px", fontWeight: "bold" }}>{hp} / 100</span>
                  </div>
                  <div style={{ height: "8px", background: "#1e293b", borderRadius: "4px", overflow: "hidden" }}>
                    <div style={{
                      height: "100%",
                      width: Math.min(hp, 100) + "%",
                      background: hp > 50
                        ? "linear-gradient(90deg, #22c55e, #4ade80)"
                        : hp > 20
                          ? "linear-gradient(90deg, #eab308, #facc15)"
                          : "linear-gradient(90deg, #ef4444, #f87171)",
                      borderRadius: "4px",
                      transition: "width 0.3s ease",
                    }} />
                  </div>
                </div>
              </div>

              {/* 武器栏 */}
              <div style={{
                display: "flex", alignItems: "center", gap: "8px", marginBottom: "14px",
                padding: "10px 14px", background: "rgba(30, 41, 59, 0.8)",
                borderRadius: "8px", border: "1px solid #475569",
              }}>
                <span style={{ fontSize: "16px" }}>⚔️</span>
                <span style={{ color: "#94a3b8", fontSize: "13px" }}>武器:</span>
                <span style={{
                  color: equippedWeapon ? "#e2e8f0" : "#475569",
                  fontSize: "13px",
                  fontWeight: equippedWeapon ? "600" : "normal",
                  fontStyle: equippedWeapon ? "normal" : "italic",
                }}>
                  {equippedWeapon || "无"}
                </span>
              </div>

              {/* 物品格子 */}
              {inventory.length === 0 ? (
                <div style={{
                  padding: "24px", 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(140px, 1fr))", gap: "8px" }}>
                  {inventory.map((item, idx) => {
                    const name = String(item?.name || item);
                    const action = itemActions[name];
                    const isEquipped = equippedWeapon === name;
                    return (
                      <div key={idx} style={{
                        display: "flex", flexDirection: "column", alignItems: "center", gap: "6px",
                        padding: "12px 8px 8px", background: "rgba(30, 41, 59, 0.8)", borderRadius: "8px",
                        border: isEquipped ? "1px solid #22d3ee" : "1px solid #475569",
                      }}>
                        <span style={{ fontSize: "28px" }}>{String(item?.icon || "📦")}</span>
                        <span style={{ color: "#e2e8f0", fontSize: "12px", fontWeight: "600", textAlign: "center" }}>{name}</span>
                        <span style={{ color: "#64748b", fontSize: "11px" }}>x{Number(item?.count ?? 1)}</span>
                        {action && (
                          <button
                            onClick={action.run}
                            style={{
                              marginTop: "4px", padding: "4px 14px", width: "100%",
                              borderRadius: "6px", fontSize: "12px", fontWeight: "600", cursor: "pointer",
                              background: action.type === "consumable" ? "#047857" : isEquipped ? "#4b5563" : "#1e40af",
                              border: "1px solid " + (action.type === "consumable" ? "#10b981" : isEquipped ? "#6b7280" : "#3b82f6"),
                              color: action.type === "consumable" ? "#a7f3d0" : isEquipped ? "#9ca3af" : "#bfdbfe",
                            }}
                          >
                            {isEquipped ? "已装备" : action.label}
                          </button>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          )}
        </div>
      );
    }} />
  );
}

代码做了什么:

  • <Chat renderBubble={...} /> 保留平台自带的消息列表、输入框和滚动;你负责画每个气泡,面板放在最后一个下面
  • api.variables.inventory、.hp 和 .equipped_weapon 按名字读变量
  • useItem 按名字找到物品,数量减一(减到零就删掉),再用 api.setVariable("inventory", next) 把整个列表写回去。药水还会让 hp 涨上去
  • equipItem 设好 equipped_weapon,再告诉玩家
  • api.injectContext(text) 给 AI 塞一句话,只管它的下一次回复,和行为里的 告诉 AI 一样
  • api.showToast(text, type) 弹一个提示;类型是 "success"、"error" 或 "info"
  • itemActions 决定哪些物品有按钮。每多一种能用的物品就加一行

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

不想自己写代码?

点画布右上角的 创作助手,描述一下就行,比如「一个物品格子,带生命条、武器栏,还有使用和装备按钮」。

AI 怎么改这个列表 ​

AI 在回复末尾用指令写列表的变化,格式引擎已经教给它了。往里加东西很靠谱:

你搜了搜哥布林,找到一个小瓶子。[inventory: push {"name":"药水","icon":"🧪","count":1}]

AI 也能按位置删([inventory: delete 0] 删掉第一件),但它没法按名字找到「药水」,也没法稳定地改某一件的数量。数量交给按钮去管,在行为规则里告诉 AI 只负责加东西。


速查 ​

你想要怎么做
一个 AI 跟着剧情往里放东西的背包玩家界面 → 背包 模板
开局就有的物品背包变量的 初始值
精确的数量(药水、箭)每种东西一个数值变量
用一件东西,先检查够不够一条行为:玩家点了某个按钮,如果…… 数量大于 0,条件不满足时告诉玩家 ……
一个能触发它的按钮按钮步骤 让一条行为生效
装备一样东西按钮步骤 改变量,改一个文字变量
做完后藏起按钮什么时候显示 → 变量满足条件时
马上告诉 AI行为里的 告诉 AI,或者代码里的 api.injectContext()
每件物品带按钮和数量的格子界面代码(见上面写代码的做法)

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

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

recipe-7-demo-zh.json

怎么导入:

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

演示用的是写代码的版本:一个 inventory 列表、hp、equipped_weapon、一条装备行为,物品格子写在界面代码里。


这是食谱 #7

背包可以和商店搭着用(金币进、物品出),也可以和任务追踪搭着用(物品当奖励)。别的列表也可以这么分:故事里的列表交给 AI,要精确数字的东西各给一个变量。