任务追踪器
构建一个任务追踪面板——显示每个任务的完成状态(对勾或叉号),实时显示金币奖励。当玩家完成任务时,自动弹出成就通知并发放奖励。本教程教你如何将变量、行为和 Root Component 连接起来。
你将构建什么
一个嵌入在聊天界面中的任务追踪面板:
- 任务列表 — 每个任务显示名称和完成状态(完成 = 绿色对勾,未完成 = 红色叉号)
- 金币计数器 — 实时显示玩家当前的金币数量
- 自动检测 — 当玩家的消息包含关键词(例如「herb」或「defeat」)时,任务自动标记为完成
- 成就通知 — 任务完成时弹出金色提示,告知玩家赚了多少金币
- 金币奖励 — 每个任务完成时自动发放金币
玩家发送一条包含「found the herbs」的消息
→ 引擎检测到玩家关键词「herb」
→ 检查条件:quest_1_complete == false?
→ 是:设置 quest_1_complete = true,添加 30 金币,弹出成就通知
→ 否:什么都不做(任务已完成)
→ 任务面板自动更新:「Find Herbs」从 ✗ 变为 ✓工作原理
这个任务系统使用三个核心机制:
- 布尔变量 + 关键词触发器 — 每个任务由一个布尔变量追踪。当玩家的消息包含特定关键词时,行为规则自动将变量设为
true - 条件检查 — 行为在触发前检查任务是否已完成。已完成的任务不会再次触发(没有重复奖励)
- Root Component 读取变量 — 面板实时读取任务状态和金币,动态渲染对勾或叉号
分步教程
第 1 步:创建变量
我们需要 5 个变量——两个用于任务完成状态,一个用于金币,还有两个用于任务名称(这样 Root Component 可以动态显示它们)。
编辑器 → 侧边栏 → 变量标签页 → 为每个点击「添加变量」
变量 1:任务 1 完成状态
| 字段 | 值 | 原因 |
|---|---|---|
| 名称 | Quest 1 Complete | 在变量列表中供你自己参考的标签 |
| ID | quest_1_complete | 行为和 Root Component 使用这个 ID 读写值 |
| 类型 | Boolean | 只有两个状态:「完成」和「未完成」 |
| 默认值 | false | 新会话开始时任务未完成 |
| 分类 | Flag | 这是一个状态标志,不是数值统计 |
| 行为规则 | Set to true when the player completes the Find Herbs quest. Behaviors auto-detect this via keywords, but you may also mark it complete at an appropriate story moment. | 告诉 AI 这个变量的含义以及何时应该改变 |
变量 2:任务 2 完成状态
| 字段 | 值 | 原因 |
|---|---|---|
| 名称 | Quest 2 Complete | 便于识别 |
| ID | quest_2_complete | 行为和 Root Component 使用 |
| 类型 | Boolean | 同样是两个状态 |
| 默认值 | false | 会话开始时未完成 |
| 分类 | Flag | 状态标志 |
| 行为规则 | Set to true when the player defeats the Forest Wolf. Behaviors auto-detect this via keywords, but you may also mark it complete at an appropriate story moment. | 告诉 AI 这个变量的含义以及何时应该改变 |
变量 3:Gold
| 字段 | 值 | 原因 |
|---|---|---|
| 名称 | Gold | 便于识别 |
| ID | gold | 任务完成时自动增加 |
| 类型 | Number | 金币是数值——需要加减运算 |
| 默认值 | 0 | 会话开始时没有金币——通过完成任务来赚取 |
| 最小值 | 0 | 防止金币变为负数 |
| 分类 | Resource | 金币是资源类变量 |
| 行为规则 | Gold is automatically awarded on quest completion. You may also add or subtract gold in the story — e.g., combat loot, trading, or theft. | 告诉 AI 金币可以在多种场景下变化 |
变量 4:任务 1 名称
| 字段 | 值 | 原因 |
|---|---|---|
| 名称 | Quest 1 Name | 便于识别 |
| ID | quest_1_name | Root Component 使用这个 ID 显示任务名称 |
| 类型 | String | 任务名称是文本 |
| 默认值 | Find Herbs | 第一个任务的名称 |
| 分类 | Custom | 只是描述性数据 |
| 行为规则 | Do not modify this variable. | 任务名称不应被更改 |
变量 5:任务 2 名称
| 字段 | 值 | 原因 |
|---|---|---|
| 名称 | Quest 2 Name | 便于识别 |
| ID | quest_2_name | Root Component 使用 |
| 类型 | String | 任务名称是文本 |
| 默认值 | Defeat the Forest Wolf | 第二个任务的名称 |
| 分类 | Custom | 描述性数据 |
| 行为规则 | Do not modify this variable. | 任务名称不应被更改 |
为什么每个变量都要写行为规则?
因为 AI 在生成回复时可以「建议」变量变更。如果你不告诉它不要碰某个变量,它可能会自行标记任务完成(例如 AI 认为「玩家找到了草药」就把 quest_1_complete 设为 true——但由于它绕过了行为逻辑,不会发放金币奖励)。行为规则字段是你给 AI 的指令——一旦写了,AI 就知道这些变量是系统控制的。
第 2 步:创建行为
这是任务系统的核心。我们需要 2 个行为,每个检测一个关键词并标记对应任务完成同时发放奖励。
编辑器 → 行为标签页 → 为每个点击「添加行为」
行为 1:完成任务「Find Herbs」
WHEN(何时检查):
| 字段 | 值 | 原因 |
|---|---|---|
| 触发类型 | 玩家说了关键词 (keyword) | 当玩家的消息包含特定文本时触发 |
| 关键词 | herb 或 found herb | 当玩家说类似「I found the herbs」时匹配 |
关键词匹配如何工作? 引擎检查玩家消息的内容——如果消息中包含关键词,就匹配成功。所以「I found the herbs in the cave」会触发,因为包含「herb」。如果你还想检测 AI 回复中的关键词,创建一个单独的行为,触发类型设为「AI said keyword」(
ai-keyword)。
ONLY IF(条件):
| 变量 | 运算符 | 值 | 原因 |
|---|---|---|---|
quest_1_complete | 等于 (eq) | false | 只在任务尚未完成时触发——防止重复奖励 |
为什么需要条件? 没有条件的话,每次有人提到「herb」奖励都会再次触发。有了
quest_1_complete == false,第一次提到 herb → 完成任务,发放奖励,标记为true。之后再提到 → 条件失败(已经是true),什么都不发生。
DO(动作):
按顺序添加这些动作:
| 动作类型 | 设置 | 效果 |
|---|---|---|
| 修改变量 | 变量 quest_1_complete,操作 set,值 true | 标记任务完成 |
| 修改变量 | 变量 gold,操作 add,值 30 | 发放 30 金币奖励 |
| 显示通知 | 消息 Quest Complete: Find Herbs! +30 gold,样式 achievement | 弹出金色成就提示 |
| Tell AI | 内容:The player just completed the quest "Find Herbs" and received 30 gold as a reward. Please acknowledge this in your response. | 让 AI 知道发生了什么,这样它可以写出更好的叙事过渡 |
为什么需要「Tell AI」? 修改变量和显示通知都是静默的系统操作——AI 本身并不知道「一个任务刚刚完成了」。添加这一步让 AI 可以在下一个回复中写出自然的后续(例如「You carefully tuck the herbs into your pack, remembering the village elder's request. The trip wasn't for nothing after all」)。
行为 2:完成任务「Defeat the Forest Wolf」
WHEN(何时检查):
| 字段 | 值 | 原因 |
|---|---|---|
| 触发类型 | 玩家说了关键词 (keyword) | 同上——玩家关键词触发器 |
| 关键词 | defeat 和 wolf | 两个词都必须出现——防止「I saw a wolf」误触发 |
多关键词匹配逻辑。 当你输入多个关键词时,消息必须同时包含所有关键词才会触发。所以「I defeated the forest wolf」会触发(包含「defeat」和「wolf」),但「I spotted a wolf」不会(只有「wolf」,没有「defeat」)。
ONLY IF(条件):
| 变量 | 运算符 | 值 | 原因 |
|---|---|---|---|
quest_2_complete | 等于 (eq) | false | 同上——防止重复触发 |
DO(动作):
| 动作类型 | 设置 | 效果 |
|---|---|---|
| 修改变量 | 变量 quest_2_complete,操作 set,值 true | 标记任务完成 |
| 修改变量 | 变量 gold,操作 add,值 50 | 发放 50 金币(击败狼更困难,所以奖励更高) |
| 显示通知 | 消息 Quest Complete: Defeat the Forest Wolf! +50 gold,样式 achievement | 弹出金色成就提示 |
| Tell AI | 内容:The player just completed the quest "Defeat the Forest Wolf" and received 50 gold as a reward. Please acknowledge this in your response. | 让 AI 知道发生了什么 |
动作执行顺序
单个行为中的动作按顺序执行。所以:标记完成 → 添加金币 → 弹出通知 → 通知 AI。这个顺序很重要——先标记变量可以确保所有后续逻辑基于最新状态。
第 3 步:在 Root Component 中添加任务追踪面板
这是让任务面板出现在聊天界面中的关键步骤。
编辑器 → Custom UI 部分 → 打开 index.tsx → 粘贴以下代码(替换默认的 return <Chat />):
export default function MyWorld() {
const api = useYumina();
const msgs = api.messages || [];
// 读取变量
const quest1Done = api.variables.quest_1_complete === true;
const quest2Done = api.variables.quest_2_complete === true;
const quest1Name = String(api.variables.quest_1_name || "Find Herbs");
const quest2Name = String(api.variables.quest_2_name || "Defeat the Forest Wolf");
const gold = Number(api.variables.gold ?? 0);
// 任务列表数据
const quests = [
{ name: quest1Name, done: quest1Done, reward: 30 },
{ name: quest2Name, done: quest2Done, reward: 50 },
];
const completedCount = quests.filter(q => q.done).length;
return (
<Chat renderBubble={(msg) => {
const isLastMsg = msg.messageIndex === msgs.length - 1;
return (
<div>
{/* 正常渲染消息文本(平台已经渲染了 HTML,直接使用 contentHtml) */}
<div
style={{ color: "#e2e8f0", lineHeight: 1.7 }}
dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
/>
{/* 任务追踪面板——只在最后一条消息上显示 */}
{isLastMsg && (
<div style={{
marginTop: "16px",
padding: "16px",
background: "linear-gradient(135deg, rgba(30,41,59,0.8), rgba(15,23,42,0.9))",
borderRadius: "12px",
border: "1px solid #334155",
}}>
{/* 面板标题栏 */}
<div style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "14px",
}}>
<div style={{
fontSize: "15px",
fontWeight: "bold",
color: "#e2e8f0",
letterSpacing: "0.5px",
}}>
Quest Tracker
</div>
{/* 金币计数器 */}
<div style={{
display: "flex",
alignItems: "center",
gap: "6px",
padding: "4px 12px",
background: "rgba(234,179,8,0.15)",
border: "1px solid rgba(234,179,8,0.3)",
borderRadius: "20px",
}}>
<span style={{ fontSize: "14px" }}>💰</span>
<span style={{
fontSize: "14px",
fontWeight: "bold",
color: "#fbbf24",
}}>
{gold}
</span>
</div>
</div>
{/* 进度指示 */}
<div style={{
fontSize: "12px",
color: "#64748b",
marginBottom: "12px",
}}>
Completed {completedCount}/{quests.length}
</div>
{/* 任务列表 */}
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{quests.map((quest, idx) => (
<div
key={idx}
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "10px 14px",
background: quest.done
? "rgba(34,197,94,0.08)"
: "rgba(30,41,59,0.5)",
border: quest.done
? "1px solid rgba(34,197,94,0.2)"
: "1px solid #1e293b",
borderRadius: "8px",
}}
>
{/* 左侧:任务名称 */}
<div style={{
display: "flex",
alignItems: "center",
gap: "10px",
}}>
<span style={{
fontSize: "13px",
color: quest.done ? "#94a3b8" : "#e2e8f0",
textDecoration: quest.done ? "line-through" : "none",
}}>
{quest.name}
</span>
</div>
{/* 右侧:状态徽章 */}
<div style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}>
{/* 奖励金额 */}
<span style={{
fontSize: "12px",
color: quest.done ? "#4ade80" : "#64748b",
}}>
{quest.done ? `+${quest.reward} g` : `${quest.reward} g`}
</span>
{/* 完成状态徽章 */}
<span style={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: "24px",
height: "24px",
borderRadius: "6px",
fontSize: "13px",
fontWeight: "bold",
background: quest.done
? "rgba(34,197,94,0.2)"
: "rgba(239,68,68,0.15)",
color: quest.done ? "#4ade80" : "#f87171",
border: quest.done
? "1px solid rgba(34,197,94,0.3)"
: "1px solid rgba(239,68,68,0.25)",
}}>
{quest.done ? "✓" : "✗"}
</span>
</div>
</div>
))}
</div>
{/* 全部完成横幅 */}
{completedCount === quests.length && (
<div style={{
marginTop: "12px",
padding: "10px",
background: "rgba(34,197,94,0.1)",
border: "1px solid rgba(34,197,94,0.25)",
borderRadius: "8px",
textAlign: "center",
fontSize: "13px",
color: "#4ade80",
fontWeight: "600",
}}>
All quests complete!
</div>
)}
</div>
)}
</div>
);
}} />
);
}代码详解
不要被代码长度吓到——它做的事情非常简单。让我们逐部分讲解:
基础设置
const api = useYumina();
const msgs = api.messages || [];
// ...
<Chat renderBubble={(msg) => {
const isLastMsg = msg.messageIndex === msgs.length - 1;
// ...
}} />- Root Component
MyWorld()是世界 UI 的入口。<Chat renderBubble={...} />让平台负责消息列表、输入框和滚动——你只接管每个气泡的外观 useYumina()— 获取 Yumina API 以读取变量msg.messageIndex— 当前气泡在消息列表中的索引。任务面板只在最后一条消息下方显示,不会在每条消息上重复msg.contentHtml— 平台已经从 Markdown 渲染好的 HTML,可以直接用于dangerouslySetInnerHTML
读取变量
const quest1Done = api.variables.quest_1_complete === true;
const quest2Done = api.variables.quest_2_complete === true;
const quest1Name = String(api.variables.quest_1_name || "Find Herbs");
const quest2Name = String(api.variables.quest_2_name || "Defeat the Forest Wolf");
const gold = Number(api.variables.gold ?? 0);=== true— 严格比较,确保只有布尔值true才算完成。防止"true"(字符串)或1(数字)被误判String(... || "Find Herbs")— 读取任务名称,如果变量不存在则回退到默认值Number(... ?? 0)— 将金币转为数字。?? 0表示「如果变量不存在就用 0」
任务列表数据
const quests = [
{ name: quest1Name, done: quest1Done, reward: 30 },
{ name: quest2Name, done: quest2Done, reward: 50 },
];
const completedCount = quests.filter(q => q.done).length;将任务信息收集到数组中,这样可以用 .map() 遍历。completedCount 统计完成数量,用于进度显示。
状态徽章
<span style={{
background: quest.done
? "rgba(34,197,94,0.2)" // 完成 → 绿色背景
: "rgba(239,68,68,0.15)", // 未完成 → 红色背景
color: quest.done ? "#4ade80" : "#f87171",
}}>
{quest.done ? "✓" : "✗"}
</span>每个任务右侧有一个小徽章——完成显示绿色对勾,未完成显示红色叉号。这就是徽章组件效果。
金币计数器
<div style={{
padding: "4px 12px",
background: "rgba(234,179,8,0.15)",
borderRadius: "20px",
}}>
💰 {gold}
</div>面板右上角的药丸形金币显示。每完成一个任务金币增加,面板自动刷新显示新值。
全部完成横幅
{completedCount === quests.length && (
<div style={{ /* 绿色高亮样式 */ }}>
All quests complete!
</div>
)}当所有任务都完成后,面板底部出现一行绿色文字。completedCount === quests.length 检查完成数是否等于总数。
不想自己写代码?使用 Studio AI
编辑器顶部 → 点击「进入 Studio」→ AI 助手面板 → 用自然语言描述你想要的效果(例如「build a quest tracker panel that shows quest completion status and gold」),AI 会帮你生成代码。
第 4 步:保存并测试
- 点击编辑器顶部的保存
- 点击开始游戏或返回主页打开新会话
- 你会在 AI 回复下方看到任务追踪面板:两个红色叉号标记的任务,0 金币
- 发送一条包含关键词的消息(例如「I found the herbs」)——你的消息包含「herb」,行为立即触发,面板更新:「Find Herbs」变为绿色对勾,金币变为 30,弹出成就通知
- 再发一条包含关键词的消息(例如「I defeated the forest wolf」)——你的消息同时包含「defeat」和「wolf」,第二个任务完成,金币增加到 80
- 两个任务都完成后,面板底部出现绿色的「All quests complete!」横幅
如果不起作用:
| 症状 | 可能原因 | 解决方法 |
|---|---|---|
| 任务面板没有出现 | Root Component 代码未保存或有语法错误 | 检查 Custom UI 部分底部的编译状态——应该显示绿色的「OK」 |
| 发送了包含「herb」的消息但任务没完成 | 行为关键词与你的实际措辞不匹配 | 确保你的消息确实包含「herb」。注意:触发器是玩家关键词触发器——只检查玩家的消息,不检查 AI 的回复 |
| 任务完成了但金币没变 | 行为中缺少「modify variable gold add」动作 | 回到行为编辑器确认在「modify variable quest_1_complete」动作之后有「modify variable gold add 30」动作 |
| 同一个任务一直重复给奖励 | 没有配置条件 | 确保行为的 ONLY IF 条件包含 quest_1_complete eq false——只在未完成时触发 |
| 面板没有实时更新 | 正常——面板在下一条消息时刷新 | 变量已经改变了;等 AI 回复或发送另一条消息,面板会自动更新 |
| 通知没有弹出 | 行为中缺少「show notification」动作 | 确认动作列表中有显示通知动作,样式设为 achievement |
| 「Defeat the wolf」任务没触发 | 两个关键词必须同时出现在同一条消息中 | 确保你的消息同时包含「defeat」和「wolf」。如果你写的是「I beat the wolf」,需要将关键词改为「beat」或添加「beat」作为替代 |
进一步:扩展任务系统
掌握了基础之后,你可以在此基础上构建更多功能。
添加更多任务
在变量标签页中添加新的布尔变量(quest_3_complete)和字符串变量(quest_3_name),然后在行为标签页中创建匹配的关键词触发行为。最后在 Root Component 的 quests 数组中添加一行:
const quests = [
{ name: quest1Name, done: quest1Done, reward: 30 },
{ name: quest2Name, done: quest2Done, reward: 50 },
{ name: quest3Name, done: quest3Done, reward: 100 },
];让 AI 分配任务
你可以构建一个「接受任务」流程——AI 在对话中描述一个新任务,然后行为检测特定关键词并动态更新任务名称变量:
| 动作类型 | 设置 |
|---|---|
| 修改变量 | 变量 quest_3_name,操作 set,值 Escort the merchant to safety |
| 显示通知 | 消息 New Quest: Escort the merchant to safety,样式 achievement |
与商店系统结合
任务赚取的金币可以在商店中消费。参见教程 #3(商店与交易)——使用相同的 gold 变量。任务系统添加金币,商店系统扣除金币。两个系统共享同一个经济体系。
任务链
你可以在行为中使用条件组合来创建复杂的任务依赖关系。例如,「只有完成了 'Find Herbs' 才能接受 'Save the Village'」:
| 变量 | 运算符 | 值 |
|---|---|---|
quest_1_complete | 等于 (eq) | true |
quest_3_complete | 等于 (eq) | false |
两个条件必须同时满足才会触发——确保前置任务已完成且当前任务尚未完成。
快速参考
| 你想做什么 | 怎么做 |
|---|---|
| 追踪任务完成 | 创建布尔变量,默认 false,分类 Flag |
| 检测关键词来完成任务 | 行为触发类型「Player said keyword」(keyword),输入关键词 |
| 防止重复触发 | 在行为条件中添加 quest_complete eq false |
| 完成时弹出成就提示 | 行为动作:显示通知,样式 achievement |
| 完成时奖励金币 | 行为动作:修改变量,gold add 数量 |
| 让 AI 知道任务完成了 | 行为动作:Tell AI,写一句解释发生了什么 |
| 显示任务面板 | 在 Root Component 中读取变量,渲染对勾/叉号和金币 |
| 只在最后一条消息上显示面板 | 在 <Chat renderBubble> 中检查 msg.messageIndex === msgs.length - 1 |
| 已完成任务加删除线 | 使用 textDecoration: "line-through" 样式 |
| 显示完成进度 | 使用 quests.filter(q => q.done).length 计数 |
| 全部完成时显示特殊横幅 | 检查 completedCount === quests.length |
试试看——可导入的演示世界
下载这个 JSON 并导入,体验完整的任务追踪系统:
如何导入:
- 前往 Yumina → 我的世界 → 创建新世界
- 在编辑器中,点击更多操作 → 导入包
- 选择下载的
.json文件 - 新世界会被创建,所有变量、行为和 Root Component 都已预配置
- 开始新会话并试试看
包含内容:
- 5 个变量(
quest_1_complete和quest_2_complete用于任务状态,gold用于货币,quest_1_name和quest_2_name用于任务名称) - 2 个行为(Find Herbs 完成 + Defeat the Forest Wolf 完成,每个都包含条件检查、变量修改、通知和 Tell AI 动作)
- 一个 Root Component(任务追踪面板:任务列表 + 状态徽章 + 金币计数器 + 完成进度)
这是教程 #6
之前的教程涵盖了场景跳转、战斗系统、商店与交易和角色创建。本教程教你如何使用布尔变量 + 关键词触发器 + 条件检查来构建任务追踪系统。相同的模式可以扩展到成就系统、故事进度追踪、支线任务树——任何需要「检测事件 → 标记状态 → 发放奖励 → 更新 UI」循环的场景。
