商店与交易
一个玩家能买东西的商店:玩家看得到自己的金币和在卖什么,点「购买」,金币扣掉,再告诉他买到了什么。金币不够的话,就告诉他不够。整个商店只要一条行为,每个按钮只负责告诉它买的是哪件、多少钱。
你要做的东西
- 金币显示在画面上
- 商店页,每件商品一个购买按钮:药水(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 步:做商店页
点顶栏中间的 玩家界面。用 新建一页 加一页(选 空白页),起名叫「商店」。
- 加一个 文字 部件,写
金币:{Gold}(用 插入变量的值…) - 每件商品加一个 文字 部件写名字和介绍,再加一个写着
购买 · 20 金币的 按钮 - 在按钮的 按下时,依次做 里加 让一条行为生效 → 购买,然后点两次 + 传一个值进去:
item=药水,price=20。铁剑就是铁剑和50 - 加一个按钮,步骤选 跳到页面,跳回对话页;再在对话页上加一个「商店」按钮跳到这一页
想把买不起的商品显示出来,就把购买按钮复制一个,把复制的那个弄成灰色的样子,再用 什么时候显示 → 变量满足条件时:灰色的那个填 Gold 小于 20,正常的那个填 Gold 大于等于 20。灰色的按钮照样能让行为生效,所以点它会弹出「金币不够」。
把 编辑 关掉就能点着试试这一页。预览不会调用 AI,但能看到金币在变。
会变的价格
价格可以来自一个变量:在 + 传一个值进去 里写 {{Potion price}},不写 20。改了这个变量(比如过节时用一条行为改),所有按钮都跟着变。
第 4 步:试玩
点顶栏的 试玩。
- 金币显示 100。打开 商店
- 买一瓶药水:金币 80,「你买了:药水」。发一条消息,AI 会提到这件事,药水出现在背包里
- 再买一瓶:金币 60
- 买铁剑:金币 10
- 随便再买点什么:「金币不够,药水要 20。」金币还是 10
试玩右边的 这回合 能看到购买行为有没有触发,没触发的话是为什么。
如果出了问题:
| 现象 | 可能的原因 | 怎么改 |
|---|---|---|
| 点购买没反应 | 按钮的 让一条行为生效 选的是别的行为,或者这条行为没 启用 | 两个都检查一下 |
| 金币没变 | 传进去的值的名字和行为里的 {param.…} 不一样 | 按钮上是 price,行为里是 {param.price},拼写要一样 |
| 金币不够时没有提示 | 条件不满足时告诉玩家 没写 | 把那句话写上 |
| 金币变成负数 | 少了条件,或者 Gold 没设最小值 | 保留 如果……,范围 从 0 开始 |
| 背包里一直没出现这件东西 | AI 没加进去 | 行为里保留 告诉 AI,背包变量的行为规则也留着,或者用上面那几种分毫不差的做法 |
写代码的做法:聊天下方的商店面板
这个版本用界面代码画商店。它检查金币时还是用 购买 这条行为,所以价格和「金币不够」的提示都留在画布上;买成以后,它自己把东西加进一个 inventory 列表。这里假设你有一个叫 inventory、初始值为 [] 的列表/表格变量。
面板 → 前端代码 → 打开 index.tsx,把默认的 return <Chat /> 换掉:
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 导入进去,就能看到能跑的版本:
怎么导入:
- 点左边的 创建,选 空白项目,再选 画布
- 在顶栏点 ⋮ → 导入文件,选下载好的
.json文件。它会替换掉这张新卡里的所有内容 - 点 保存,再点 试玩
演示里每件商品用两条行为(买到了 / 金币不够),商店面板写在界面代码里。
这是食谱 #3
按钮把值传给同一条行为,这个办法适用于任何有价格、从菜单里选的东西:制作、训练、雇人。见行为 · 按钮传值。
