日夜循环
做一个自动推进的时间系统——每 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-crossed | 2 条 | 想理解底层原理的人 |
| 方式 B:每 N 回合直接触发 | turn-count(everyNTurns=3) | 1 条 | 想简单搞定的人 |
本配方教方式 A(更通用,也能帮你理解行为系统的联动机制)。文末会简要说明方式 B 的差异。
一步步来
第 1 步:创建变量
我们需要 2 个变量——一个记录当前时段,一个当计数器。
编辑器 → 左侧边栏 → 变量 标签页 → 逐个点击「添加变量」
变量 1:当前时段
| 字段 | 填什么 | 为什么这样填 |
|---|---|---|
| 显示名称 | 当前时段 | 给你自己看的,方便识别 |
| ID | time_period | 行为和消息渲染器用这个 ID 来读写 |
| 类型 | 字符串 | 因为值是文字("早晨"、"中午"、"傍晚"、"夜晚") |
| 默认值 | 早晨 | 新会话从早晨开始 |
| 分类 | 自定义 | 时间系统专用分类 |
| 行为规则 | 不要修改这个变量。它由日夜循环系统自动控制。当前值代表游戏内的时段。 | 告诉 AI 不要自作主张改时间——只有行为规则能改 |
变量 2:回合计数器
| 字段 | 填什么 | 为什么这样填 |
|---|---|---|
| 显示名称 | 回合计数器 | 给你自己看的 |
| ID | turn_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 | 晨曲 | BGM | 是 | 2 秒 | 2 秒 |
bgm_noon | 午后 | BGM | 是 | 2 秒 | 2 秒 |
bgm_evening | 黄昏 | BGM | 是 | 2 秒 | 2 秒 |
bgm_night | 夜曲 | BGM | 是 | 2 秒 | 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」→ 粘贴以下代码:
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 步:保存并测试
- 点击编辑器顶部的「保存」
- 点击「开始游戏」或回到首页开一个新会话
- 正常和 AI 对话。前 2 回合你会看到时间徽章显示「☀️ 早晨」,什么都不会变
- 第 3 回合结束后——时间推进到「🌤️ 中午」,AI 的下一段回复会自然地提到时间变化
- 再过 3 回合——推进到「🌅 傍晚」
- 再过 3 回合——推进到「🌙 夜晚」。如果你配了 BGM,此时应该能听到渐变切歌
- 再过 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-crossed | turn-count(everyNTurns=3) |
需要 turn_counter 变量 | 是 | 否 |
| 行为数量 | 6 条(1 计数 + 4 推进 + 可选 1 初始化) | 4 条(4 推进) |
| 灵活性 | 高(可以动态调整间隔) | 低(间隔固定为 N) |
| 适合 | 需要动态调节时间流速的世界 | 固定节奏的世界 |
方式 B 怎么做:
去掉 turn_counter 变量和「每回合计数」行为。4 条时段推进行为的触发器全部改为:
| 字段 | 填什么 |
|---|---|
| 触发器类型 | 每 N 回合 |
| everyNTurns | 3 |
其他的(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 文件,导入即可体验完整效果:
导入方法:
- 进入 Yumina → 我的世界 → 创建新世界
- 在编辑器顶部点「更多操作」→「导入包」
- 选择下载的
.json文件 - 世界会被创建,所有变量、条目、行为和渲染器都已预配置好
- 开一个新会话试试看
包含内容:
- 2 个变量(
time_period追踪当前时段,turn_counter回合计数器) - 4 个知识条目(早晨 / 中午 / 傍晚 / 夜晚氛围,默认只启用早晨)
- 6 条行为(1 条每回合计数 + 4 条时段推进 + 1 条会话初始化)
- 一个消息渲染器(时段图标徽章)
- 4 条 BGM 音轨(需要你自己上传音频文件替换 URL)
这是实战配方 #9
这个配方展示了行为系统的联动威力——用简单的计数器 + 阈值触发 + 条件分支,就能搭出一个完全自动的时间系统。同样的模式可以用来做天气变化、季节轮替、NPC 心情波动等任何"按节奏自动变化"的东西。
