时间与调度
游戏采用双层时间体系:底层 Tick 调度系统按 8 档频率驱动各系统逻辑更新,上层游戏历法以“1 现实秒 = 1 游戏小时”(1 倍速)的速率推进天/小时/分钟,一个游戏天只需 24 现实秒;玩家通过右上角时间条在暂停/1x/2x/5x 四档之间切换全局速度。
时间与调度是整个游戏的节拍器,决定了“游戏世界里过了多久”和“每个系统多久更新一次”两件事:
- 游戏历法(
DateTimeManager.cs):维护游戏内的天/小时/分钟。发薪、食物腐烂、商人补货、新闻事件等所有“按天结算”的玩法都挂在它的跨天事件上。 - 全局速度(
GameSpeedManager.cs):通过 Unity 引擎的全局时间缩放实现暂停与加速,一个开关同时影响所有运动、动画、Tick 调度和历法推进。 - Tick 调度(
TickManager.cs/TickGroup.cs/TickFrequency.cs):把“不需要每帧更新”的系统按 8 档固定频率分组调度,降低性能开销。任务分配、生产进度、船员需求等都不是每帧跑的,而是各自挂在某一档频率上。
玩家接触时间系统的入口:
- 右上角时间条(
TimeBarPanel.cs,见界面总览):显示“第 X 天 HH:00”(只精确到小时),右侧四个速度按钮(暂停 / 1x / 2x / 5x)。 - 快捷键:Space 切换暂停/恢复;Tab 在 1x → 2x → 5x 之间循环(跳过暂停档)。
- 自动暂停:打开主菜单(
MainMenuPanel.cs)或胜利结算画面(VictoryScreen.cs)时自动暂停,关闭时恢复。 - 开发者工具(
DevToolsPanel.cs,见开发者工具):额外提供 0.1x ~ 10x 调速和“跳转到第 X 天 X 时”。
参考设计:RimWorld / Factorio 的“固定逻辑节拍 + 可变播放速度”模式。
游戏历法:分钟、小时、天
Section titled “游戏历法:分钟、小时、天”游戏内时间的最小单位是游戏分钟,历法结构与现实一致(均为 DateTimeManager.cs 代码常量):
| 单位 | 换算 | 出处 |
|---|---|---|
| 1 游戏小时 | 60 游戏分钟 | DateTimeManager.cs 常量 MINUTES_PER_HOUR |
| 1 游戏天 | 24 游戏小时 = 1440 游戏分钟 | DateTimeManager.cs 常量 HOURS_PER_DAY / MINUTES_PER_DAY |
推进规则(每帧执行,凑满 1 整游戏分钟才入账):
游戏分钟增量 = 现实流逝秒数 × 60 × 全局速度倍率 × 日期时间倍率- 全局速度倍率:玩家选的 1x/2x/5x(见下节)。
- 日期时间倍率:
DateTimeManager上叠加在速度档之上的第二个倍率字段_timeScale(Unity Inspector 序列化字段,可调范围 0~10,主场景mvp_002.unity中当前值为 1)。通常保持 1,仅作整体调速的备用旋钮;设为 0 时历法完全停走(但游戏世界仍在动)。
由此得到默认换算锚点:1 倍速下,1 现实秒 = 60 游戏分钟 = 1 游戏小时;1 游戏天 = 24 现实秒。
时间推进的两个保护与事件规则:
- 单帧最多推进 1000 游戏分钟(约 16.7 游戏小时),超出部分留到下一帧并打警告日志(
DateTimeManager.cs代码常量MAX_TICKS_PER_FRAME),防止极端倍率下单帧卡死。 - 每推进 1 游戏分钟检查一次时间事件:整小时跳变时推送时间条刷新;跨天时向全游戏广播“新的一天”事件(
OnDayChanged),这是所有日级玩法的统一触发源。
开发者工具面板提供“跳转到第 X 天 X 时”(DevToolsManager.cs / DevToolsPanel.cs),跳转会清空分钟累积量并立即触发对应的小时/天事件。
全局速度与暂停
Section titled “全局速度与暂停”GameSpeedManager.cs 把速度倍率直接写入 Unity 引擎的全局时间缩放,因此一个数值同时控制:所有移动和动画、子弹飞行、Tick 调度节奏、游戏历法推进。不存在“只加速时间不加速战斗”的拆分。
| 规则 | 行为 | 出处 |
|---|---|---|
| 速度档位(时间条) | 暂停(0) / 1x / 2x / 5x | TimeBarPanel.cs 代码常量 SpeedPresets |
| 速度档位(开发者工具) | 0.1x / 0.5x / 1x / 2x / 5x / 10x + 暂停 | DevToolsPanel.cs 代码常量 TimeScales |
| 速度下限/上限 | 不允许负值,0 即暂停;代码层无上限 | GameSpeedManager.cs |
| 快捷键 Space | 切换暂停/恢复 | TimeBarPanel.cs |
| 快捷键 Tab | 循环 1x → 2x → 5x → 1x(跳过暂停档) | TimeBarPanel.cs |
| 暂停记忆 | 暂停前的速度被记住,恢复时回到原速;若无记录则回 1x | GameSpeedManager.cs |
| 游戏启动 | 强制重置为 1x | GameSpeedManager.cs |
自动暂停场景:
- 主菜单打开时自动暂停(半透明遮罩直观提示已暂停),关闭时若是它触发的暂停则自动恢复(
MainMenuPanel.cs)。 - 胜利结算画面打开时同样自动暂停、关闭恢复(
VictoryScreen.cs)。
例外:城市交易的金币飞行动画刻意不受时间缩放影响,暂停时也会继续播完(WorldGoldFlyEffect.cs)。
Tick 调度系统:分频更新
Section titled “Tick 调度系统:分频更新”TickManager.cs 维护 8 个频率组,各系统注册到某一组后按该组节拍收到更新回调。频率档位(TickFrequency.cs 代码常量,枚举值即毫秒间隔):
| 档位 | 间隔 | 等价频率 |
|---|---|---|
| EveryFrame | 每帧 | 跟随帧率 |
| TenTimesPerSecond | 100 毫秒 | 10 次/秒 |
| FiveTimesPerSecond | 200 毫秒 | 5 次/秒 |
| TwicePerSecond | 500 毫秒 | 2 次/秒 |
| OncePerSecond | 1000 毫秒 | 1 次/秒 |
| EveryTwoSeconds | 2000 毫秒 | 0.5 次/秒 |
| EveryFiveSeconds | 5000 毫秒 | 0.2 次/秒 |
| EveryTenSeconds | 10000 毫秒 | 0.1 次/秒 |
注意:这里的“秒”是受全局速度缩放的引擎秒,不是现实秒。2 倍速下“每秒 1 次”的组实际每 0.5 现实秒触发一次;暂停时所有定频组完全停摆。由于历法同样按引擎秒推进,任何速度档下 Tick 档位与游戏时间的对应关系都恒定(见下方速查表)。
调度规则(TickGroup.cs):
- 固定步长回调:定频组每次触发传给系统的时间增量恒等于组间隔(如 1 秒组每次传 1.0 秒),与实际帧间隔无关,保证逻辑结果与帧率解耦。
- 不补帧:如果因卡顿或暂停导致落后超过 2 倍组间隔,直接重置计时基准,丢弃错过的周期而不是连续补执行——防止恢复瞬间逻辑爆发。
- 暂停行为:定频组停摆;EveryFrame 组仍每帧回调但时间增量为 0(依赖时间增量的逻辑自然冻结)。
- 同组优先级:注册时可带优先级数值,数值越小越先执行(
ITickable.cs)。 - 异常隔离:某个对象的回调抛异常时被踢出调度组,不影响同组其他对象。
各系统的更新节奏
Section titled “各系统的更新节奏”游戏内主要系统的注册频率(均为各文件中的代码常量,改频率需要程序修改):
| 系统 | 频率 | 每次更新做什么 | 出处 |
|---|---|---|---|
| 游戏历法推进 | 每帧 | 累积游戏分钟、触发小时/天事件 | DateTimeManager.cs |
| 生产制作进度 | 10 次/秒 | 推进当前配方制作进度、产出成品 | ProductionComponent.cs |
| 引擎动力输出 | 10 次/秒 | 根据生产效率平滑引擎动力值(0~1) | EnginePowerComponent.cs |
| 流体管网 | 10 次/秒 | 管道网络流体输送模拟 | FluidSystemManager.cs |
| 任务分配中枢 | 5 次/秒 | 收集任务请求 → 创建任务 → 分配给船员 | TaskManager.cs |
| 敌船 AI 状态机 | 5 次/秒 | 巡逻/追击/攻击/撞击/逃跑决策 | ShipAIStateMachine.cs |
| 门 | 5 次/秒 | 开门倒计时推进、自动关门判定 | DoorComponent.cs |
| 船员需求 | 1 次/秒 | 饥饿/口渴/疲劳/排泄增长、危急状态切换、自然回血、失意到期检查、死亡判定 | Crew.cs |
| 船员对话 | 每 5 秒 | 消费对话事件队列或轮流闲聊一句 | CrewDialogueManager.cs |
| 容器内容弹窗(开发工具) | 2 次/秒 | 刷新容器物品显示 | BuildingContainerInfoPopup.cs |
| 战斗/武器/子弹/怪物/移动/寻路执行 | 每帧 | 直接走 Unity 每帧更新,自动受全局速度影响 | Bullet.cs、BaseMonster.cs、PathfindingAgent.cs 等 |
经验法则:位置和画面相关的逻辑每帧跑;决策和资源类逻辑 5~10 次/秒;状态结算 1 次/秒以下;经济和事件按天结算。
日级事件:跨天触发的玩法
Section titled “日级事件:跨天触发的玩法”以下系统全部由历法的“新的一天”事件驱动(暂停时不会跨天,加速时按比例加快):
| 系统 | 周期 | 触发行为 | 关键数值与出处 |
|---|---|---|---|
| 薪资发放 | 每 4 游戏天 | 按职级从高到低发薪;金币不足则欠薪计数 +1 | 周期 salaryIntervalDays = 4(EconomyConfig.cs 默认值,见下文调参) |
| 招募候选人刷新 | 每个发薪周期(4 天) | 进港时按周期号刷新候选人池 | RecruitmentManager.cs |
| 食物保质期 | 每天 | 所有容器内带保质期物品年龄 +1;超过 新鲜天数+陈旧天数 整堆变为腐烂物;恰好到期前一天发预警 | 各物品的 FreshDays / StaleDays 来自物品 CSV 配置表,见物品数据表;逻辑在 ShelfLifeSystem.cs |
| 商人库存 | 每天 | 每个商人按日补货量刷新库存 | TraderInventory.cs |
| 世界新闻 | 每 7 游戏天 | 投放 1 条新闻(宣战/和平/物价波动/怪潮) | 间隔 IntervalDays = 7(EventScheduler.cs 代码常量) |
| 船员心情 | 每天 | 全员心情想法(Thought)每日结算 | MoodSystem.cs |
| 船员关系 | 每天 + 每 7 天 | 每天按上下级(+0.5/+0.3)/副手(+1.0)/同侪(+0.3)/同专业(+0.2)累积好感;每 7 天所有好感 ×0.85 衰减 | RelationshipManager.Tick.cs 代码常量 |
| 长航压力 | 每天 | 不靠岸超过 14 天后,玩家船每名船员每天 +2 压力 | STRESS_NO_PORT_DAYS = 14、STRESS_NO_PORT_PER_DAY = 2(NeedsConfig.cs 代码常量) |
| 失意状态消退 | 60 天到期(1 次/秒检查) | 被降职的失意状态满 60 天自然消退 | DEMOTION_DURATION_DAYS = 60(CrewStatus.cs 代码常量) |
调试与手动步进(开发期)
Section titled “调试与手动步进(开发期)”TickManager支持全局手动步进模式:开启后自动调度停止,可逐 Tick 推进所有组或单独推进某个频率组;手动步进时按频率间隔传固定时间增量(每帧组按 60 帧/秒折算为 1/60 秒)。TickDebugger.cs提供运行时快捷键(F3 开关调试界面、小键盘 1 单步、分号键切换手动模式,均为 Inspector 序列化字段可改键),但该组件未放置在主场景mvp_002中,需手动挂载才生效。- Unity 编辑器菜单
Tools → Tick System Debugger与Tools → Tick System Stats提供分组监控窗口(TickSystemDebugWindow.cs/TickSystemStatsWindow.cs)。 - 场景测试运行器执行测试套件时临时把全局速度调到 3 倍加速跑测试(
ScenarioTestRunner.csInspector 序列化字段_testTimeScale,默认 3,范围 1~5),结束后还原。
实际历法流逝速度 = 全局速度倍率 × 日期时间倍率(默认 1)游戏分钟增量(每帧) = 帧时长(现实秒) × 60 × 实际历法流逝速度当前天数 = 累计游戏分钟 ÷ 1440(向下取整)当前小时 = (累计游戏分钟 mod 1440) ÷ 60(向下取整)当前分钟 = 累计游戏分钟 mod 60时间单位换算速查表(策划设计数值用)
Section titled “时间单位换算速查表(策划设计数值用)”锚点:1 倍速下 1 现实秒 = 1 游戏小时(DateTimeManager.cs,场景日期时间倍率 = 1)。
| 游戏时间 | 现实时间 @1x | @2x | @5x |
|---|---|---|---|
| 1 游戏分钟 | 1/60 秒 ≈ 0.017 秒 | 0.008 秒 | 0.003 秒 |
| 1 游戏小时 | 1 秒 | 0.5 秒 | 0.2 秒 |
| 1 游戏天 | 24 秒 | 12 秒 | 4.8 秒 |
| 4 游戏天(发薪周期) | 96 秒 = 1.6 分钟 | 48 秒 | 19.2 秒 |
| 7 游戏天(新闻/关系衰减周期) | 168 秒 = 2.8 分钟 | 84 秒 | 33.6 秒 |
| 14 游戏天(长航压力阈值) | 336 秒 = 5.6 分钟 | 168 秒 | 67.2 秒 |
| 60 游戏天(失意消退) | 1440 秒 = 24 分钟 | 12 分钟 | 4.8 分钟 |
反向速查——Tick 频率档对应的游戏时间(任何速度档下恒定,因为 Tick 与历法同被缩放):
| Tick 档位 | 引擎间隔 | 对应游戏时间 |
|---|---|---|
| EveryFrame(60 帧/秒时) | ≈16.7 毫秒 | ≈1 游戏分钟/帧 |
| 10 次/秒 | 100 毫秒 | 6 游戏分钟 |
| 5 次/秒 | 200 毫秒 | 12 游戏分钟 |
| 2 次/秒 | 500 毫秒 | 30 游戏分钟 |
| 1 次/秒 | 1 秒 | 1 游戏小时 |
| 每 2 秒 | 2 秒 | 2 游戏小时 |
| 每 5 秒 | 5 秒 | 5 游戏小时 |
| 每 10 秒 | 10 秒 | 10 游戏小时 |
给策划的换算口诀:代码里写“每秒 X”的速率,在游戏世界里就是“每游戏小时 X”。例如船员饥饿增长 0.3/秒(NeedsConfig.cs),等价于每游戏小时 +0.3、每游戏天 +7.2。
| 数值 | 值 | 单位 | 来源类型 | 出处 |
|---|---|---|---|---|
| 每游戏小时分钟数 | 60 | 游戏分钟 | 代码常量 | DateTimeManager.cs |
| 每游戏天小时数 | 24 | 游戏小时 | 代码常量 | DateTimeManager.cs |
| 默认历法速率 | 1 现实秒 = 60 游戏分钟 | — | 代码逻辑 | DateTimeManager.cs |
| 日期时间倍率 | 1(范围 0~10) | 倍率 | Inspector 序列化字段 | DateTimeManager.cs,场景 mvp_002.unity |
| 单帧时间推进上限 | 1000 | 游戏分钟/帧 | 代码常量 | DateTimeManager.cs |
| 时间条速度档 | 0 / 1 / 2 / 5 | 倍率 | 代码常量 | TimeBarPanel.cs |
| 开发者工具速度档 | 0.1 / 0.5 / 1 / 2 / 5 / 10 | 倍率 | 代码常量 | DevToolsPanel.cs |
| Tick 频率档位 | 8 档(每帧 ~ 每 10 秒) | 毫秒 | 代码常量 | TickFrequency.cs |
| Tick 落后重置阈值 | 2 × 组间隔 | 秒(引擎秒) | 代码常量 | TickGroup.cs |
| 发薪周期 | 4 | 游戏天 | Inspector 序列化字段(ScriptableObject 默认值) | EconomyConfig.cs |
| 新闻投放间隔 | 7 | 游戏天 | 代码常量 | EventScheduler.cs |
| 关系周衰减 | ×0.85 每 7 天 | 倍率/游戏天 | 代码常量 | RelationshipManager.Tick.cs |
| 长航压力阈值/增量 | 14 天后每天 +2 | 游戏天、压力值 | 代码常量 | NeedsConfig.cs |
| 失意消退时长 | 60 | 游戏天 | 代码常量 | CrewStatus.cs |
| 场景测试加速 | 3(范围 1~5) | 倍率 | Inspector 序列化字段 | ScenarioTestRunner.cs |
与其他系统的交互
Section titled “与其他系统的交互”- 任务与工作:任务分配中枢以 5 次/秒的节奏收集请求、创建并分配任务;船员需求触发的吃喝睡任务由 1 次/秒的需求结算产生。
- 船员:需求增长、自然回血、死亡判定全部挂在 1 秒组;薪资(4 天)、失意(60 天)、长航压力(14 天阈值)挂在日级事件上。
- 船员社交与关系:好感日累积与 7 天周衰减由跨天事件驱动。
- 生产与制作:制作进度、引擎动力、流体管网都在 10 次/秒组;配方耗时以秒计,受全局速度加速。
- 物品与库存:保质期按游戏天老化,新鲜/陈旧天数来自物品配置表。
- 交易:商人库存每游戏天刷新一次。
- 探索与情报:世界新闻每 7 游戏天投放一条,并直接改写阵营关系或全局物价。
- 遭遇与刷怪:怪物移动与攻击为每帧更新,敌船 AI 决策为 5 次/秒,全部随全局速度缩放。
- 界面总览:右上角时间条是速度控制的玩家入口;时间显示只精确到整小时(“第 X 天 HH:00”),分钟不上屏。
- 开发者工具:开发面板提供 0.1~10x 调速、暂停和“跳转到第 X 天 X 时”。
- 存档:新闻列表与投放游标会持久化;当前游戏时间本身的存取见存档页。
配置与调参指南
Section titled “配置与调参指南”| 想调什么 | 改哪里 | 类型 |
|---|---|---|
| 一游戏天对应的现实时长 | DateTimeManager 的日期时间倍率字段(主场景 mvp_002.unity 中该对象的 Inspector,当前 1;调到 2 则一天 12 现实秒)。注意它只加速历法,不加速战斗/移动,会改变“每引擎秒 = 1 游戏小时”的对应关系 |
Inspector 序列化字段 |
| 整体游戏节奏(含战斗) | 玩家侧用时间条档位;要改档位本身(如加 3x 档)需程序改 TimeBarPanel.cs 的 SpeedPresets / SpeedLabels 常量 |
代码常量 |
| 开发调试速度档 | DevToolsPanel.cs 的 TimeScales 数组 |
代码常量 |
| 某系统的更新频率 | 该系统构造 Tick 组件处的频率参数(见“各系统的更新节奏”表的出处列),需程序修改 | 代码常量 |
| 发薪周期、候选人刷新周期 | EconomyConfig.cs 的 salaryIntervalDays。当前项目中不存在 Resources/Config/EconomyConfig.asset 资产,运行时使用代码默认值 4 并打告警日志;策划可自建该 ScriptableObject 资产后在 Inspector 里改,无需程序 |
Inspector 序列化字段(资产缺失时为代码默认值) |
| 新闻投放间隔 | EventScheduler.cs 的 IntervalDays 常量,需程序修改 |
代码常量 |
| 食物腐烂快慢 | 物品 CSV 配置表的 FreshDays / StaleDays 列,策划可直接改,见物品数据表 | CSV 配置表 |
| 时间条外观 | Assets/UI/Top/TimeBar/TimeBarPanel.uxml / .uss |
UI 资源 |
| 场景测试加速倍率 | ScenarioTestRunner 的 Inspector 字段(范围 1~5) |
Inspector 序列化字段 |
已知限制与注意事项
Section titled “已知限制与注意事项”- “固定 60 tick/秒”是设计意图,不是实现现状:
DateTimeManager.cs头部注释宣称固定 60 tick/秒、与帧率解耦,实际实现是逐帧累积时间增量(长期均值等价,但不是固定步长模拟,单帧内时间推进量随帧率波动)。 - 注释与实现的换算冲突:
NeedsConfig.cs中 BAC(血液酒精浓度)衰减速率的注释写“1 游戏小时 = 60 现实秒”,与DateTimeManager.cs的“1 现实秒 = 1 游戏小时”相差 60 倍。代码行为以DateTimeManager为准:BAC 实际每游戏小时只衰减 5/60 ≈ 0.083,而非注释意图的每游戏小时 -5。涉及“每秒”速率的数值设计需统一确认时间锚点。 - “每秒”速率的真实含义:所有 Tick 回调里的速率(需求增长、回血、紧张衰减等)按受速度缩放的引擎秒计,等价于“每游戏小时”(在日期时间倍率 = 1 时)。若未来调整日期时间倍率,这些速率与历法的对应关系会整体偏移。
- 低频组不补周期:卡顿或暂停恢复后,错过的 Tick 周期直接丢弃(设计如此,防爆发);按 Tick 次数累计的逻辑在长暂停后不会追账。
- 时间跳转只触发一次跨天事件:开发者工具从第 1 天直接跳到第 10 天只广播一次“新的一天”,中间 8 天的日级结算(关系日累积、每日压力等)不会补执行;薪资因采用“距上次发薪天数 ≥ 周期”判定可自我纠正。自然推进(包括极高倍速)则逐分钟推进、每个跨天都会触发。
- 时间显示只到小时:时间条按整小时跳变刷新,分钟级变化玩家不可见(内部有含分钟的完整显示字符串,仅开发者工具使用)。
- 暂停时每帧组仍在跑:注册在每帧组的逻辑暂停期间仍被回调(时间增量为 0),不依赖时间增量的副作用(如查询、刷新)不会被暂停冻结。
- Tick 模块 README 文档漂移:
Assets/Scripts/Service/Common/Tick/README.md提到的 PauseGroup / ResumeGroup 接口在TickManager.cs中不存在(实际为手动模式开关 SetGroupManualMode)。 - TickDebugger 未挂场景:运行时 Tick 调试快捷键需手动把
TickDebugger组件挂进场景才可用。 - Notion 设计文档为空:
Docs/Notion/设计/基础系统/时间与tick系统文档只有标题,无内容;本页全部以代码为准。