Skip to content

任务追踪器

构建一个任务追踪面板——显示每个任务的完成状态(对勾或叉号),实时显示金币奖励。当玩家完成任务时,自动弹出成就通知并发放奖励。本教程教你如何将变量、行为和 Root Component 连接起来。


你将构建什么

一个嵌入在聊天界面中的任务追踪面板:

  • 任务列表 — 每个任务显示名称和完成状态(完成 = 绿色对勾,未完成 = 红色叉号)
  • 金币计数器 — 实时显示玩家当前的金币数量
  • 自动检测 — 当玩家的消息包含关键词(例如「herb」或「defeat」)时,任务自动标记为完成
  • 成就通知 — 任务完成时弹出金色提示,告知玩家赚了多少金币
  • 金币奖励 — 每个任务完成时自动发放金币
玩家发送一条包含「found the herbs」的消息
  → 引擎检测到玩家关键词「herb」
  → 检查条件:quest_1_complete == false?
    → 是:设置 quest_1_complete = true,添加 30 金币,弹出成就通知
    → 否:什么都不做(任务已完成)
  → 任务面板自动更新:「Find Herbs」从 ✗ 变为 ✓

工作原理

这个任务系统使用三个核心机制:

  1. 布尔变量 + 关键词触发器 — 每个任务由一个布尔变量追踪。当玩家的消息包含特定关键词时,行为规则自动将变量设为 true
  2. 条件检查 — 行为在触发前检查任务是否已完成。已完成的任务不会再次触发(没有重复奖励)
  3. Root Component 读取变量 — 面板实时读取任务状态和金币,动态渲染对勾或叉号

分步教程

第 1 步:创建变量

我们需要 5 个变量——两个用于任务完成状态,一个用于金币,还有两个用于任务名称(这样 Root Component 可以动态显示它们)。

编辑器 → 侧边栏 → 变量标签页 → 为每个点击「添加变量」

变量 1:任务 1 完成状态

字段原因
名称Quest 1 Complete在变量列表中供你自己参考的标签
IDquest_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便于识别
IDquest_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便于识别
IDgold任务完成时自动增加
类型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便于识别
IDquest_1_nameRoot Component 使用这个 ID 显示任务名称
类型String任务名称是文本
默认值Find Herbs第一个任务的名称
分类Custom只是描述性数据
行为规则Do not modify this variable.任务名称不应被更改

变量 5:任务 2 名称

字段原因
名称Quest 2 Name便于识别
IDquest_2_nameRoot 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)当玩家的消息包含特定文本时触发
关键词herbfound 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)同上——玩家关键词触发器
关键词defeatwolf两个词都必须出现——防止「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 />):

tsx
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>
      );
    }} />
  );
}

代码详解

不要被代码长度吓到——它做的事情非常简单。让我们逐部分讲解:

基础设置

tsx
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

读取变量

tsx
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」

任务列表数据

tsx
const quests = [
  { name: quest1Name, done: quest1Done, reward: 30 },
  { name: quest2Name, done: quest2Done, reward: 50 },
];
const completedCount = quests.filter(q => q.done).length;

将任务信息收集到数组中,这样可以用 .map() 遍历。completedCount 统计完成数量,用于进度显示。

状态徽章

tsx
<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>

每个任务右侧有一个小徽章——完成显示绿色对勾,未完成显示红色叉号。这就是徽章组件效果。

金币计数器

tsx
<div style={{
  padding: "4px 12px",
  background: "rgba(234,179,8,0.15)",
  borderRadius: "20px",
}}>
  💰 {gold}
</div>

面板右上角的药丸形金币显示。每完成一个任务金币增加,面板自动刷新显示新值。

全部完成横幅

tsx
{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 步:保存并测试

  1. 点击编辑器顶部的保存
  2. 点击开始游戏或返回主页打开新会话
  3. 你会在 AI 回复下方看到任务追踪面板:两个红色叉号标记的任务,0 金币
  4. 发送一条包含关键词的消息(例如「I found the herbs」)——你的消息包含「herb」,行为立即触发,面板更新:「Find Herbs」变为绿色对勾,金币变为 30,弹出成就通知
  5. 再发一条包含关键词的消息(例如「I defeated the forest wolf」)——你的消息同时包含「defeat」和「wolf」,第二个任务完成,金币增加到 80
  6. 两个任务都完成后,面板底部出现绿色的「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 数组中添加一行:

tsx
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 并导入,体验完整的任务追踪系统:

recipe-6-demo.json

如何导入:

  1. 前往 Yumina → 我的世界创建新世界
  2. 在编辑器中,点击更多操作导入包
  3. 选择下载的 .json 文件
  4. 新世界会被创建,所有变量、行为和 Root Component 都已预配置
  5. 开始新会话并试试看

包含内容:

  • 5 个变量(quest_1_completequest_2_complete 用于任务状态,gold 用于货币,quest_1_namequest_2_name 用于任务名称)
  • 2 个行为(Find Herbs 完成 + Defeat the Forest Wolf 完成,每个都包含条件检查、变量修改、通知和 Tell AI 动作)
  • 一个 Root Component(任务追踪面板:任务列表 + 状态徽章 + 金币计数器 + 完成进度)

这是教程 #6

之前的教程涵盖了场景跳转、战斗系统、商店与交易和角色创建。本教程教你如何使用布尔变量 + 关键词触发器 + 条件检查来构建任务追踪系统。相同的模式可以扩展到成就系统、故事进度追踪、支线任务树——任何需要「检测事件 → 标记状态 → 发放奖励 → 更新 UI」循环的场景。