Unity中如何实现每秒数百/数千次的稳定时间递减计算?
问题根源
你当前写法出现长时长计时偏差的核心原因是对WaitForSeconds的精度有错误假设:
- Unity协程的
WaitForSeconds调度受帧率、引擎主线程负载影响,单次实际等待时长几乎不可能刚好等于你传入的0.01f,帧率低、主线程卡顿的时候单次等待可能达到几十毫秒 - 你固定每次循环给
subtractTime减1的逻辑,默认每次等待刚好是10ms,累计误差会随着时长增加不断放大,6秒场景下误差会累积到几百毫秒甚至秒级,感知非常明显 - 逐次递减的逻辑本身就没有精度可言,哪怕把等待间隔压到更小,也解决不了累计误差问题,反而会增加不必要的性能开销
优化方案
彻底放弃固定步长逐次减1的计时逻辑,改用时间差直接计算剩余值的方案,计时精度完全不依赖协程等待频率,哪怕每秒只刷新10次也不会有误差:
- 项目激活时,先记录启动时间戳和配置好的项目总时长
- 每次循环直接用当前时间减去启动时间,算出已经流逝的时长,直接推导剩余的
subtractTime值 - 剩余值归零时标记完成,退出循环即可
代码实现
版本1:受游戏时间缩放影响(游戏暂停时计时同步暂停)
适合和游戏进程绑定、不需要计算离线收益的普通计时场景:
// 初始化时把原来乘100的时长转成秒单位,处理等级带来的时长减半逻辑 float totalDuration = subtractTime / 100f; // 满足等级条件直接减半时长 if (currentProjectLevel >= targetReduceLevel) { totalDuration /= 2f; } float startGameTime = Time.time; completed = false; while (true) { // 直接计算已流逝时长,推导剩余值,不需要逐次减1 float elapsedTime = Time.time - startGameTime; subtractTime = Mathf.Max(0, (int)((totalDuration - elapsedTime) * 100)); if (subtractTime <= 0) { break; } // 等待频率根据UI刷新需求设置即可,哪怕yield return null每帧执行也不会有额外精度问题 // 不需要刻意追求每秒上千次执行,60帧每秒的刷新频率人眼完全感知不到卡顿 yield return null; } completed = true;
版本2:真实时间计时(不受帧率、暂停影响,支持离线收益计算)
适合放置类游戏的核心计时逻辑,切后台、游戏暂停都不会影响计时准确性:
float totalDuration = subtractTime / 100f; if (currentProjectLevel >= targetReduceLevel) { totalDuration /= 2f; } // 记录UTC时间戳,避免玩家修改本地时间带来的计时异常 DateTime startTime = DateTime.UtcNow; completed = false; while (true) { TimeSpan elapsedRealTime = DateTime.UtcNow - startTime; subtractTime = Mathf.Max(0, (int)((totalDuration - (float)elapsedRealTime.TotalSeconds) * 100)); if (subtractTime <= 0) { break; } // 按需设置刷新间隔,0.01f~0.05f都可以,完全不影响计时精度 yield return new WaitForSeconds(0.02f); } completed = true;
额外说明
- 不需要追求每秒数百/数千次的递减频率,计时精度和循环执行次数没有关系,只要最终时间计算准确即可,过高的刷新频率只会带来无意义的性能开销
- 防重复激活的逻辑直接在激活入口判断
completed状态或者subtractTime > 0即可,不需要额外加互斥逻辑 - 如果要做离线收益,只需要在游戏启动时读取上次退出的时间戳,直接计算离线期间完成的项目和收益即可,不需要保持协程持续后台运行
内容的提问来源于stack exchange,提问作者RequisDaGod
相关产品推荐
相关产品推荐

