11 · 时间事件系统
时间事件系统是灵泛引擎的特色功能,让游戏世界随时间自行运转。本章讲解回调驱动的时间事件。
两种游戏模式
灵泛引擎支持两种互斥的模式:
| 模式 | EnableTimeSystem | 特点 |
|---|---|---|
| 叙事模式 | false(默认) | 静态世界,玩家推动才变化,支持逐句回溯 |
| 小说世界模式 | true | 动态世界,时间驱动自行运转,禁用逐句回溯 |
两种模式互斥
EnableTimeSystem 是硬开关。启用时间系统后,逐句回溯被禁用(改为场景级时间锚点)。
深入了解小说世界模式
小说世界模式有完整的设计哲学——时间事件生命周期、场景名称即坐标、时间锚点、NPC 时间尊严等。详见 进阶专题 · 小说世界模式。
时间配置
在 LingFanEngineOptions 中配置:
| 配置 | 默认值 | 说明 |
|---|---|---|
EnableTimeSystem | false | 是否启用时间系统 |
SecondsPerGameMinute | 1.0 | 现实多少秒 = 游戏多少分钟 |
TimeStartDay | 1 | 起始天数 |
TimeStartHour | 0 | 起始小时 |
TimeStartMinute | 0 | 起始分钟 |
时间控制
启动时间
启用时间系统后,时间默认不会自动运行,需要显式启动:
dsl
time_resume暂停 / 恢复
dsl
time_pause // 暂停游戏时间
time_resume // 恢复游戏时间跳过时间
dsl
skip_time 60 // 跳过 60 分钟
skip_time 1440 // 跳过 1 天skip_time 逐分钟推进,确保中间时间点的事件被检查。
时间事件
set_time_event 注册事件
dsl
set_time_event "morning_event" 8
say "早上 8 点,阳光照进窗户。" speaker="旁白"
set "player.energy" {player.energy + 10}| 参数 | 说明 |
|---|---|
| 第一个参数 | 事件 ID(唯一标识) |
| 小时 | 触发小时(0-23) |
minute=N | 分钟精度(可选,默认整点) |
day=N | 天数(可选) |
once=true | 是否只触发一次(默认 false) |
weekdays="Mon,Tue" | 星期过滤(可选) |
condition="{expr}" | 条件表达式(可选) |
desc="描述" | 描述(可选) |
缩进块内是事件触发时执行的代码。
4 层 AND 过滤
事件触发需要同时满足 4 个条件:
- 小时——
e.Hour == currentHour - 分钟——null=整点(minute==0 触发),指定值=精确匹配
- 天数——null=不限制;once=true→绝对天;once=false→间隔天
- 星期——null/空=每天,非空=匹配星期
天数语义
dsl
// 第 3 天触发一次
set_time_event "day3_event" 10 day=3 once=true
// 每 2 天触发一次
set_time_event "every_2days" 10 day=2 once=falseonce=true+day=N——第 N 天触发一次once=false+day=N——每 N 天触发一次
星期过滤
dsl
set_time_event "weekday_event" 9 weekdays="Mon,Tue,Wed,Thu,Fri"
say "工作日早上 9 点。" speaker="旁白"星期缩写:Mon/Tue/Wed/Thu/Fri/Sat/Sun
条件过滤
dsl
set_time_event "conditional" 12 condition="{player.hp > 50}"
say "你身体健康,中午散步。" speaker="旁白"unregister_time_event 注销
dsl
unregister_time_event "morning_event"全局事件 vs 场景事件
事件来源分两类:
| 类型 | 说明 | 注册方式 |
|---|---|---|
| 全局事件 | 直接全局注册 | set_time_event |
| 场景挂载事件 | 由场景声明注册 | C# InTimeEvents 特性 |
事件生命周期
时间事件一旦注册就独立存在,不绑定到注册它的场景。场景只是事件的"出生地",事件可以在任意时间影响任意场景。
全局事件 vs 场景事件
事件来源分两类(两者都走 EventScheduler,区别只是挂载时机):
- 全局事件——开局即注册的事件(如春夏秋冬、日夜更替),本质上也是通过
set_time_event/SetTimeEventAsync/InTimeEvents()注册到 EventScheduler 的时间事件,并非引擎内部有另一套"自适应计算"系统 - 场景挂载事件——由场景声明注册的事件,一旦挂载就独立运行,场景只是它的"出生地"
存档与时间事件
存档
- 导航驱动事件(
IsLegacyNavigation=true)——序列化为列表 - 回调驱动事件——只持久化 ID 集合和已触发记录
读档
- 清空 + 恢复已触发记录
- 导航事件直接恢复
- 回调事件在场景初始化时重新注册
C# API
csharp
// 注册回调驱动时间事件(返回 void,无需 await)
gameController.SetTimeEventAsync(
id: "morning_event",
hour: 8,
callback: async () =>
{
await gameController.SayAsync("早上 8 点。", speaker: "旁白");
},
once: false,
weekdays: null,
minute: 0,
day: null);
// 注销
gameController.UnregisterEvent("morning_event");动手练习
注册一个每天早上 8 点的事件:
dsl
define "player.energy" 100 once
set_time_event "morning_wake" 8 once=false desc="每天早上醒来"
say "早上 8 点,你醒了过来。" speaker="旁白"
set "player.energy" 100
say "精力完全恢复!" speaker="系统"
set_time_event "night_sleep" 22 once=false desc="每天晚上睡觉"
say "晚上 10 点,你感到困倦。" speaker="旁白"
set "player.energy" {player.energy - 30}
if {player.energy < 0}
set "player.energy" 0
say "你太累了。" speaker="系统"下一步
时间系统掌握了!下一章学习用 C# 扩展——突破 DSL 的边界。