Skip to content

时间与调度

This content is not available in your language yet.

游戏采用双层时间体系:底层 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 的“固定逻辑节拍 + 可变播放速度”模式。

游戏内时间的最小单位是游戏分钟,历法结构与现实一致(均为 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),跳转会清空分钟累积量并立即触发对应的小时/天事件。

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)。

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)。
  • 异常隔离:某个对象的回调抛异常时被踢出调度组,不影响同组其他对象。

游戏内主要系统的注册频率(均为各文件中的代码常量,改频率需要程序修改):

系统 频率 每次更新做什么 出处
游戏历法推进 每帧 累积游戏分钟、触发小时/天事件 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.csBaseMonster.csPathfindingAgent.cs

经验法则:位置和画面相关的逻辑每帧跑;决策和资源类逻辑 5~10 次/秒;状态结算 1 次/秒以下;经济和事件按天结算

以下系统全部由历法的“新的一天”事件驱动(暂停时不会跨天,加速时按比例加快):

系统 周期 触发行为 关键数值与出处
薪资发放 每 4 游戏天 按职级从高到低发薪;金币不足则欠薪计数 +1 周期 salaryIntervalDays = 4EconomyConfig.cs 默认值,见下文调参)
招募候选人刷新 每个发薪周期(4 天) 进港时按周期号刷新候选人池 RecruitmentManager.cs
食物保质期 每天 所有容器内带保质期物品年龄 +1;超过 新鲜天数+陈旧天数 整堆变为腐烂物;恰好到期前一天发预警 各物品的 FreshDays / StaleDays 来自物品 CSV 配置表,见物品数据表;逻辑在 ShelfLifeSystem.cs
商人库存 每天 每个商人按日补货量刷新库存 TraderInventory.cs
世界新闻 每 7 游戏天 投放 1 条新闻(宣战/和平/物价波动/怪潮) 间隔 IntervalDays = 7EventScheduler.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 = 14STRESS_NO_PORT_PER_DAY = 2NeedsConfig.cs 代码常量)
失意状态消退 60 天到期(1 次/秒检查) 被降职的失意状态满 60 天自然消退 DEMOTION_DURATION_DAYS = 60CrewStatus.cs 代码常量)
  • TickManager 支持全局手动步进模式:开启后自动调度停止,可逐 Tick 推进所有组或单独推进某个频率组;手动步进时按频率间隔传固定时间增量(每帧组按 60 帧/秒折算为 1/60 秒)。
  • TickDebugger.cs 提供运行时快捷键(F3 开关调试界面、小键盘 1 单步、分号键切换手动模式,均为 Inspector 序列化字段可改键),但该组件未放置在主场景 mvp_002,需手动挂载才生效。
  • Unity 编辑器菜单 Tools → Tick System DebuggerTools → Tick System Stats 提供分组监控窗口(TickSystemDebugWindow.cs / TickSystemStatsWindow.cs)。
  • 场景测试运行器执行测试套件时临时把全局速度调到 3 倍加速跑测试(ScenarioTestRunner.cs Inspector 序列化字段 _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
  • 任务与工作:任务分配中枢以 5 次/秒的节奏收集请求、创建并分配任务;船员需求触发的吃喝睡任务由 1 次/秒的需求结算产生。
  • 船员:需求增长、自然回血、死亡判定全部挂在 1 秒组;薪资(4 天)、失意(60 天)、长航压力(14 天阈值)挂在日级事件上。
  • 船员社交与关系:好感日累积与 7 天周衰减由跨天事件驱动。
  • 生产与制作:制作进度、引擎动力、流体管网都在 10 次/秒组;配方耗时以秒计,受全局速度加速。
  • 物品与库存:保质期按游戏天老化,新鲜/陈旧天数来自物品配置表。
  • 交易:商人库存每游戏天刷新一次。
  • 探索与情报:世界新闻每 7 游戏天投放一条,并直接改写阵营关系或全局物价。
  • 遭遇与刷怪:怪物移动与攻击为每帧更新,敌船 AI 决策为 5 次/秒,全部随全局速度缩放。
  • 界面总览:右上角时间条是速度控制的玩家入口;时间显示只精确到整小时(“第 X 天 HH:00”),分钟不上屏。
  • 开发者工具:开发面板提供 0.1~10x 调速、暂停和“跳转到第 X 天 X 时”。
  • 存档:新闻列表与投放游标会持久化;当前游戏时间本身的存取见存档页。
想调什么 改哪里 类型
一游戏天对应的现实时长 DateTimeManager 的日期时间倍率字段(主场景 mvp_002.unity 中该对象的 Inspector,当前 1;调到 2 则一天 12 现实秒)。注意它只加速历法,不加速战斗/移动,会改变“每引擎秒 = 1 游戏小时”的对应关系 Inspector 序列化字段
整体游戏节奏(含战斗) 玩家侧用时间条档位;要改档位本身(如加 3x 档)需程序改 TimeBarPanel.csSpeedPresets / SpeedLabels 常量 代码常量
开发调试速度档 DevToolsPanel.csTimeScales 数组 代码常量
某系统的更新频率 该系统构造 Tick 组件处的频率参数(见“各系统的更新节奏”表的出处列),需程序修改 代码常量
发薪周期、候选人刷新周期 EconomyConfig.cssalaryIntervalDays。当前项目中不存在 Resources/Config/EconomyConfig.asset 资产,运行时使用代码默认值 4 并打告警日志;策划可自建该 ScriptableObject 资产后在 Inspector 里改,无需程序 Inspector 序列化字段(资产缺失时为代码默认值)
新闻投放间隔 EventScheduler.csIntervalDays 常量,需程序修改 代码常量
食物腐烂快慢 物品 CSV 配置表的 FreshDays / StaleDays 列,策划可直接改,见物品数据表 CSV 配置表
时间条外观 Assets/UI/Top/TimeBar/TimeBarPanel.uxml / .uss UI 资源
场景测试加速倍率 ScenarioTestRunner 的 Inspector 字段(范围 1~5) Inspector 序列化字段
  • “固定 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系统 文档只有标题,无内容;本页全部以代码为准。