Skip to content

日夜循环

做一个自动推进的时间系统——每 3 回合,时间自动前进(早晨→中午→傍晚→夜晚→早晨)。不同时段启用不同的知识条目和背景音乐,AI 的描写氛围随之变化。玩家什么都不用做,时间就在流淌。


你要做的东西

一个嵌入在聊天里的日夜循环系统:

  • 自动计时——每次对话回合,内部计数器 +1。到第 3 回合时,时间前进一个时段
  • 四个时段——早晨 → 中午 → 傍晚 → 夜晚 → 早晨(循环往复)
  • 氛围切换——每个时段有自己的知识条目,描述那个时段的光线、温度、NPC 行为等。切换时段时,旧条目禁用、新条目启用
  • 背景音乐切换——早晨播鸟鸣晨曲,夜晚换成虫鸣蛙叫,切换时用渐变过渡(crossfade),不会突然断掉
  • 时间徽章——聊天界面最后一条消息上显示当前时段图标(☀️🌤️🌅🌙),玩家一眼就知道现在是什么时候

原理

整个系统的核心是:每回合计数器 +1 → 计数器达到 3 → 切换时段 → 重置计数器 → 开关条目和音乐

玩家发消息 → AI 回复 → 回合结束
  → 「每回合 +1」行为触发:turn_counter 从 0 变成 1
  → 下一回合:turn_counter 从 1 变成 2
  → 再下一回合:turn_counter 从 2 变成 3
  → 「变量越过阈值」行为触发:turn_counter 上穿 2
  → 行为执行:time_period 设为下一个时段,turn_counter 重置为 0
  → 禁用旧时段条目,启用新时段条目
  → 渐变切换到新时段的 BGM
  → 消息渲染器更新时间徽章

有两种实现方式,效果一样,但适合不同的理解习惯:

方式用到的触发器行为数量适合谁
方式 A:每回合 +1 + 变量越过阈值every-turn + variable-crossed2 条想理解底层原理的人
方式 B:每 N 回合直接触发turn-count(everyNTurns=3)1 条想简单搞定的人

本配方教方式 A(更通用,也能帮你理解行为系统的联动机制)。文末会简要说明方式 B 的差异。


一步步来

第 1 步:创建变量

我们需要 2 个变量——一个记录当前时段,一个当计数器。

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

变量 1:当前时段

字段填什么为什么这样填
显示名称当前时段给你自己看的,方便识别
IDtime_period行为和消息渲染器用这个 ID 来读写
类型字符串因为值是文字("早晨""中午""傍晚""夜晚"
默认值早晨新会话从早晨开始
分类自定义时间系统专用分类
行为规则不要修改这个变量。它由日夜循环系统自动控制。当前值代表游戏内的时段。告诉 AI 不要自作主张改时间——只有行为规则能改

变量 2:回合计数器

字段填什么为什么这样填
显示名称回合计数器给你自己看的
IDturn_counter每回合 +1,到 3 就重置
类型数字需要做加法运算
默认值0从 0 开始计数
分类自定义时间系统专用分类
行为规则不要修改这个变量。它由日夜循环系统自动控制。防止 AI 擅自修改

为什么用计数器而不是直接每 3 回合触发?

用计数器 + 变量越过阈值的方式更灵活。比如你以后想做"白天 3 回合切换,夜晚 5 回合切换",只需要在行为里加一个条件判断就行。直接用 turn-count 触发器虽然更简单,但灵活性稍低。两种方式各有优势,看你的需求。


第 2 步:创建四个时段知识条目

每个时段需要一个知识条目,描述该时段的环境氛围。默认只启用「早晨」,其他三个禁用。

编辑器 → 知识库 标签页 → 逐个新建条目

条目 1:早晨氛围

字段填什么为什么这样填
名称早晨氛围给你自己看的
区域预设预设区的条目每次都会发给 AI
启用(打开开关)游戏从早晨开始,所以默认启用

内容:

[当前时段:早晨]
现在是清晨。描写场景时请体现以下氛围:
- 柔和的晨光从东方洒入,空气清新微凉
- 露珠挂在草叶和花瓣上,折射出细碎的光芒
- 鸟儿在枝头鸣叫,远处传来公鸡的啼鸣
- NPC 刚刚醒来,店铺陆续开张,街上行人渐多
- 整体氛围是宁静、充满希望的

条目 2:中午氛围

字段填什么为什么这样填
名称中午氛围给你自己看的
区域预设预设区
启用(关闭开关)等行为规则在时段切换时打开

内容:

[当前时段:中午]
现在是正午。描写场景时请体现以下氛围:
- 阳光直射,光线炽热明亮,地面反射着刺眼的白光
- 空气闷热,远处的景物在热浪中微微扭曲
- 大部分人躲在阴凉处休息,街道上比早晨安静
- 酒馆和餐馆最为热闹,食物的香气飘散在空气中
- 整体氛围是慵懒、炎热的

条目 3:傍晚氛围

字段填什么为什么这样填
名称傍晚氛围给你自己看的
区域预设预设区
启用(关闭开关)等行为规则打开

内容:

[当前时段:傍晚]
现在是黄昏。描写场景时请体现以下氛围:
- 夕阳将天空染成橘红和紫色,云层边缘镶着金边
- 长长的影子从建筑和树木上投射出来
- 归巢的鸟群掠过天际,炊烟从屋顶升起
- NPC 陆续收工回家,孩子们在街上追逐玩耍
- 整体氛围是温暖、怀旧、略带伤感的

条目 4:夜晚氛围

字段填什么为什么这样填
名称夜晚氛围给你自己看的
区域预设预设区
启用(关闭开关)等行为规则打开

内容:

[当前时段:夜晚]
现在是深夜。描写场景时请体现以下氛围:
- 月光和星光是唯一的自然光源,银白色的光洒在万物之上
- 大部分建筑已经熄灯,偶尔有窗户透出昏黄的烛光
- 夜风带着凉意,虫鸣声和蛙叫声此起彼伏
- 街道几乎空无一人,巡夜的卫兵举着火把缓缓走过
- 暗处可能潜伏着危险——野兽、盗贼、或更诡异的东西
- 整体氛围是神秘、静谧、暗藏危机的

为什么默认只启用「早晨」? 因为游戏从早晨开始。如果四个条目全部启用,AI 会同时收到早晨、中午、傍晚、夜晚的描写指令,不知道该听哪个。每次只启用一个,AI 就能准确把握当前氛围。


第 3 步:(可选)上传时段 BGM

如果你想让每个时段有不同的背景音乐,需要先上传音频文件。

编辑器 → 音频 标签页 → 添加音轨

音轨 ID名称类型循环淡入淡出
bgm_morning晨曲BGM2 秒2 秒
bgm_noon午后BGM2 秒2 秒
bgm_evening黄昏BGM2 秒2 秒
bgm_night夜曲BGM2 秒2 秒

没有音频文件怎么办? 可以跳过这一步。日夜循环的核心是知识条目切换,BGM 是锦上添花。你随时可以后续补上。

在 BGM 播放列表里,把 autoPlay 设为 true,默认播放 bgm_morning。后续时段切换时,行为规则会用 crossfade 动作渐变切歌。


第 4 步:创建行为规则

这是整个系统的核心。我们需要 2 条行为。

编辑器 → 行为 标签页 → 逐个添加行为

行为 1:每回合计数

这条行为非常简单——每次对话回合结束后,计数器 +1。

WHEN(什么时候检查):

字段填什么为什么这样填
触发器类型每回合每次玩家和 AI 完成一轮对话后自动触发

DO(做什么):

动作类型设置作用
修改变量turn_counter 加上 1计数器 +1

就这么一个动作。不需要条件,不需要其他配置。每回合忠实地 +1。

为什么不直接在这里检查"到 3 了吗"? 因为行为系统的设计哲学是每条行为做一件事。让计数器 +1 是一条行为的事,检查是否到 3 是另一条行为的事。引擎会自动把它们串联起来——计数器 +1 后,如果值跨过了阈值,另一条行为就会被触发。


行为 2:时段推进

这条行为在计数器达到 3 时触发,执行所有切换逻辑。

WHEN(什么时候检查):

字段填什么为什么这样填
触发器类型变量越过阈值turn_counter 的值上穿阈值时触发
变量turn_counter我们监控的是回合计数器
方向上升(rises above)值从低于阈值变为高于阈值时触发
阈值2当 turn_counter 从 2 变成 3(上穿 2)时触发

为什么阈值是 2 而不是 3? variable-crossed 的"上升"方向检测的是值从 <= 阈值变到 > 阈值的那个瞬间。turn_counter 从 2 变成 3 时,它"上穿了 2"——也就是从 <=2 变到了 >2。如果阈值设成 3,那需要 turn_counter 从 3 变成 4 才会触发,这不是我们要的。

DO(做什么):

这条行为需要做很多事,按顺序添加以下动作:

#动作类型设置作用
1修改变量turn_counter 设为 0重置计数器,开始下一轮 3 回合倒计时
2禁用知识条目早晨氛围先把所有时段条目全部关掉
3禁用知识条目中午氛围全部关掉
4禁用知识条目傍晚氛围全部关掉
5禁用知识条目夜晚氛围全部关掉

等一下——这样不是四个时段全关了吗?怎么知道要启用哪一个?

好问题。这里需要用到 ONLY IF 条件来做分支。但一条行为只能有一组动作。所以我们把「时段推进」拆成 5 条行为:1 条负责计数器重置和全部关闭条目,4 条负责分别启用对应时段。

让我重新组织一下:


完整的行为列表(共 6 条):

行为 1:每回合计数

(和上面一样,不重复。)

行为 2:时段推进 —— 早晨→中午

WHEN:

字段填什么
触发器类型变量越过阈值
变量turn_counter
方向上升(rises above)
阈值2

ONLY IF:

变量运算符
time_period等于 (eq)早晨

DO:

#动作类型设置作用
1修改变量turn_counter 设为 0重置计数器
2修改变量time_period 设为 中午推进到下一时段
3禁用知识条目早晨氛围关掉旧时段条目
4启用知识条目中午氛围打开新时段条目
5播放音乐bgm_noon,操作:crossfade,渐变时长 3 秒渐变切换到中午 BGM
6告诉 AI内容:时间从早晨推进到了中午。请在接下来的描写中自然地体现时间变化。让 AI 在下一段回复里自然过渡

行为 3:时段推进 —— 中午→傍晚

WHEN: 和行为 2 一样(变量越过阈值,turn_counter 上穿 2)

ONLY IF:

变量运算符
time_period等于 (eq)中午

DO:

#动作类型设置作用
1修改变量turn_counter 设为 0重置计数器
2修改变量time_period 设为 傍晚推进到傍晚
3禁用知识条目中午氛围关掉中午条目
4启用知识条目傍晚氛围打开傍晚条目
5播放音乐bgm_evening,操作:crossfade,渐变时长 3 秒渐变切换 BGM
6告诉 AI内容:时间从中午推进到了傍晚。请在接下来的描写中自然地体现时间变化。AI 过渡描写

行为 4:时段推进 —— 傍晚→夜晚

WHEN: 同上

ONLY IF:

变量运算符
time_period等于 (eq)傍晚

DO:

#动作类型设置作用
1修改变量turn_counter 设为 0重置计数器
2修改变量time_period 设为 夜晚推进到夜晚
3禁用知识条目傍晚氛围关掉傍晚条目
4启用知识条目夜晚氛围打开夜晚条目
5播放音乐bgm_night,操作:crossfade,渐变时长 3 秒渐变切换 BGM
6告诉 AI内容:时间从傍晚推进到了夜晚。请在接下来的描写中自然地体现时间变化。AI 过渡描写

行为 5:时段推进 —— 夜晚→早晨

WHEN: 同上

ONLY IF:

变量运算符
time_period等于 (eq)夜晚

DO:

#动作类型设置作用
1修改变量turn_counter 设为 0重置计数器
2修改变量time_period 设为 早晨循环回早晨
3禁用知识条目夜晚氛围关掉夜晚条目
4启用知识条目早晨氛围打开早晨条目
5播放音乐bgm_morning,操作:crossfade,渐变时长 3 秒渐变切换 BGM
6告诉 AI内容:时间从夜晚推进到了早晨,新的一天开始了。请在接下来的描写中自然地体现时间变化。AI 过渡描写

为什么要拆成 4 条行为? 因为每个时段切换需要启用不同的条目、播放不同的 BGM。一条行为只能有一组条件和一组动作,不支持 if-else 分支。所以我们用 4 条行为 + 不同的 ONLY IF 条件来模拟分支:同一个触发器(计数器上穿 2)触发时,引擎会检查每一条,但只有 time_period 匹配的那一条会执行。

行为 6:会话初始化

这条行为在会话开始时设定初始状态,确保变量在新会话或重新进入时处于正确的起始值。

WHEN:

字段填什么为什么这样填
触发器类型会话开始 (session-start)每次新会话开始时自动触发一次

DO:

#动作类型设置作用
1修改变量time_period 设为 早晨确保从早晨开始
2修改变量turn_counter 设为 0重置回合计数器

为什么需要会话初始化行为? 变量的默认值只在首次创建时生效。如果玩家中途退出再重新开始会话,变量可能保留上次的值(比如 time_period 停留在"夜晚"、turn_counter 停留在 2)。会话初始化行为确保每次新会话都从早晨、计数器 0 开始。

行为的优先级

4 条时段推进行为的优先级可以都设为默认值(0)。因为它们的 ONLY IF 条件互斥——当前时段只可能匹配其中一条,不会冲突。


第 5 步:做时间徽章消息渲染器

在聊天界面最后一条消息上显示当前时段的小图标,让玩家一眼知道现在几点。

编辑器 → 消息渲染器 标签页 → 选「自定义 TSX」→ 粘贴以下代码:

tsx
export default function Renderer({ content, renderMarkdown, messageIndex }) {
  const api = useYumina();

  // ---- 读取变量 ----
  const timePeriod = String(api.variables.time_period || "早晨");

  // ---- 时段对应的图标和颜色 ----
  const timeConfig = {
    "早晨": { icon: "☀️", label: "早晨", color: "#fbbf24", bg: "rgba(251,191,36,0.15)" },
    "中午": { icon: "🌤️", label: "中午", color: "#f59e0b", bg: "rgba(245,158,11,0.15)" },
    "傍晚": { icon: "🌅", label: "傍晚", color: "#f97316", bg: "rgba(249,115,22,0.15)" },
    "夜晚": { icon: "🌙", label: "夜晚", color: "#818cf8", bg: "rgba(129,140,248,0.15)" },
  };

  const current = timeConfig[timePeriod] || timeConfig["早晨"];

  // ---- 判断是否是最后一条消息 ----
  const msgs = api.messages || [];
  const isLastMsg = messageIndex === msgs.length - 1;

  return (
    <div>
      {/* 正常渲染消息文字 */}
      <div
        style={{ color: "#e2e8f0", lineHeight: 1.7 }}
        dangerouslySetInnerHTML={{ __html: renderMarkdown(content) }}
      />

      {/* 时间徽章——只在最后一条消息上显示 */}
      {isLastMsg && (
        <div style={{
          display: "inline-flex",
          alignItems: "center",
          gap: "6px",
          marginTop: "12px",
          padding: "4px 12px",
          background: current.bg,
          border: `1px solid ${current.color}33`,
          borderRadius: "999px",
          fontSize: "13px",
          color: current.color,
          fontWeight: "600",
        }}>
          <span style={{ fontSize: "16px" }}>{current.icon}</span>
          <span>{current.label}</span>
        </div>
      )}
    </div>
  );
}

代码逐行解释:

  • api.variables.time_period — 读取当前时段变量的值
  • timeConfig — 一个映射表,把每个时段对应到图标、文字标签和颜色。你可以改颜色来适配你的世界风格
  • isLastMsg — 只在最后一条消息上显示徽章,不是每条消息都显示
  • 徽章用 inline-flex + border-radius: 999px 做成药丸形状,视觉上不抢眼但一眼就能看到

想让每条消息都显示时间?

去掉 {isLastMsg && ...} 的判断,把徽章部分直接放进 return 里就行。这样每条消息都会带一个时间标记,像聊天记录的时间戳一样。


第 6 步:保存并测试

  1. 点击编辑器顶部的「保存」
  2. 点击「开始游戏」或回到首页开一个新会话
  3. 正常和 AI 对话。前 2 回合你会看到时间徽章显示「☀️ 早晨」,什么都不会变
  4. 第 3 回合结束后——时间推进到「🌤️ 中午」,AI 的下一段回复会自然地提到时间变化
  5. 再过 3 回合——推进到「🌅 傍晚」
  6. 再过 3 回合——推进到「🌙 夜晚」。如果你配了 BGM,此时应该能听到渐变切歌
  7. 再过 3 回合——循环回到「☀️ 早晨」,新的一天开始了

如果遇到问题:

现象可能的原因解决方法
时间永远不变「每回合计数」行为没触发检查行为 1 的触发器是否设成「每回合」,且行为是启用状态
第 3 回合没切换阈值设错了确认「变量越过阈值」的阈值是 2(不是 3),方向是「上升」
切换后条目没变化条目 ID 不匹配检查行为里「启用知识条目」/「禁用知识条目」选择的条目是否和你创建的条目名称一致
四条行为同时触发ONLY IF 条件没写每条时段推进行为都必须有 ONLY IF 条件,限定当前 time_period 的值
看不到时间徽章消息渲染器代码有语法错误检查消息渲染器底部的编译状态,应该显示绿色「OK」
BGM 没切换音轨 ID 不匹配或没上传音频确认行为里的 trackId 和音频标签页里的音轨 ID 一致

方式 B 对比:用 turn-count 触发器

如果你觉得方式 A 太多行为了,可以用更简单的方式 B。

区别:

方式 A(本配方)方式 B
触发器every-turn + variable-crossedturn-count(everyNTurns=3)
需要 turn_counter 变量
行为数量6 条(1 计数 + 4 推进 + 可选 1 初始化)4 条(4 推进)
灵活性高(可以动态调整间隔)低(间隔固定为 N)
适合需要动态调节时间流速的世界固定节奏的世界

方式 B 怎么做:

去掉 turn_counter 变量和「每回合计数」行为。4 条时段推进行为的触发器全部改为:

字段填什么
触发器类型每 N 回合
everyNTurns3

其他的(ONLY IF 条件、DO 动作)和方式 A 一模一样。turn-count 触发器会自动每 3 回合触发一次,不需要你手动计数。

turn-count 触发器的工作原理: 引擎内部维护一个全局回合计数。当你设置 everyNTurns: 3 时,引擎会在第 3、6、9、12... 回合自动触发这条行为。你不需要自己管理计数器变量。


速查表

你想做的事怎么做
每回合做点什么行为触发器选「每回合」(every-turn)
每 N 回合做点什么行为触发器选「每 N 回合」(turn-count),填 everyNTurns
检测变量跨过某个值行为触发器选「变量越过阈值」(variable-crossed),填变量、方向和阈值
切换知识条目动作用「启用知识条目」/「禁用知识条目」
渐变切歌动作用「播放音乐」,操作选 crossfade,填渐变时长
让 AI 知道发生了什么动作用「告诉 AI」,写一段临时系统指令
在消息上显示状态徽章消息渲染器里读取变量,用 JSX 渲染
模拟 if-else 分支多条行为用同一个触发器 + 不同的 ONLY IF 条件

直接试试——可导入的示例世界

下载这个 JSON 文件,导入即可体验完整效果:

recipe-9-demo-zh.json

导入方法:

  1. 进入 Yumina → 我的世界 → 创建新世界
  2. 在编辑器顶部点「更多操作」→「导入包」
  3. 选择下载的 .json 文件
  4. 世界会被创建,所有变量、条目、行为和渲染器都已预配置好
  5. 开一个新会话试试看

包含内容:

  • 2 个变量(time_period 追踪当前时段,turn_counter 回合计数器)
  • 4 个知识条目(早晨 / 中午 / 傍晚 / 夜晚氛围,默认只启用早晨)
  • 6 条行为(1 条每回合计数 + 4 条时段推进 + 1 条会话初始化)
  • 一个消息渲染器(时段图标徽章)
  • 4 条 BGM 音轨(需要你自己上传音频文件替换 URL)

这是实战配方 #9

这个配方展示了行为系统的联动威力——用简单的计数器 + 阈值触发 + 条件分支,就能搭出一个完全自动的时间系统。同样的模式可以用来做天气变化、季节轮替、NPC 心情波动等任何"按节奏自动变化"的东西。