Sleep 的真实计时:30 tick 上限与逐帧动画
你写 Sleep(0.01),引擎却按 1/30 秒给你结账。本页整理社区实测数据,告诉你 Sleep 在真实服务器上到底睡多久,以及做平滑动画时为什么「Sleep(0.0) 就完事了」,连 DeltaTime 都常常不用算。
一、实测数据:Sleep 并不按你写的睡
Epic 开发者论坛帖《Important Notes On Sleep & UEFN Timing》做了一件朴素而宝贵的事:实机测量。作者在循环里放不同参数的 Sleep(就是蓝图里那个 Delay 节点),数每秒实际执行多少次,结论非常干脆——Sleep(0.1) 稳定得到每秒 10 次;而 Sleep(0.01) 和 Sleep(0.0) 都封顶在每秒约 30 次,作者在最慢与最快的测试设备上跑,结果一致。
原因正是第 19 课讲的模拟帧:Fortnite 服务器每秒约 30 次模拟更新,Sleep 只能在帧边界醒来,亚帧精度根本不存在。你写进括号的数字更像一个「下限申请」:引擎保证至少睡这么久,然后在下一个帧边界叫醒你。对照表如下:
| 你写的代码 | 实际执行频率 | 说明 |
|---|---|---|
Sleep(1.0) |
约每秒 1 次 | 正常按秒计时 |
Sleep(0.1) |
约每秒 10 次 | 正常,10 ≤ 30 装得下 |
Sleep(0.01) |
约每秒 30 次(封顶) | 想要 100 次?引擎只给 30 |
Sleep(0.0) |
约每秒 30 次(逐帧) | 「下一帧叫我」,与 0.01 实际等效 |
二、实用结论:逐帧动画直接 Sleep(0.0)
要用 Verse 做平滑移动、渐变、逐帧动画,别纠结「该睡多久」——直接写 Sleep(0.0),即每帧执行一次、约 30fps。这样意图最清晰:「我要逐帧做事」,而不是伪装成某个精确毫秒数。更省心的是,由于 tick 率被引擎调控且基本恒定,帖子作者的经验是通常不需要自己计算 DeltaTime:每帧挪一个固定步长,视觉上就是匀速运动。
用第 19 课的「loop + Sleep」模板写一个逐帧计数器,亲手验证 30 tick 这件事:
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
frame_counter_device := class(creative_device):
OnBegin<override>()<suspends>:void =
var Frames:int = 0
loop:
set Frames += 1
if (Frames >= 90):
break
# 每帧让出一次:约 30 帧 = 1 秒
Sleep(0.0)
Print("数到 {Frames} 帧,墙上时钟大约走了 3 秒")
读图翻译:这就是第 19 课那套循环——一条绕回自己的执行线,每圈先用 Set 把 Frames 加 1,再用 Branch 判断 Frames 是否到 90、到了就 Break 跳出;没到就走一个 Sleep(0.0)(时长为 0 的 Delay,等于「下一帧再来」)。跳出后用 Print String 报告一共数了多少帧。
把它拖进关卡运行:从 OnBeginOnBegin(Event BeginPlay) 到打印,真实时间约 3 秒——90 帧 ÷ 30 帧每秒。如果你把 Sleep(0.0) 换成 Sleep(0.01),结果几乎不变;换成 Sleep(0.1),就要等约 9 秒。数字不会骗人:决定节奏的是帧,不是你写的毫秒。
最后一盆冷水:帖子里也有人提醒,30 tick 对某些高速动画并不够平滑——子弹拖尾、快速镜头运动在 30fps 下会有可见的顿挫。这是引擎层面的硬限制,Verse 层无法突破;设计高速视觉效果时,要么接受 30fps,要么改用引擎侧的非 Verse 方案来表现。
顺手总结三条可以直接抄进项目的经验:第一,凡是「每 X 秒触发一次」的玩法逻辑(刷怪、回血、积分结算),X 在 0.1 秒以上时 Sleep 都很可靠,放心用;第二,凡是「逐帧」的表现逻辑,统一写 Sleep(0.0),并按 30fps 来标定每帧步长;第三,别靠数 Sleep 的次数来累计精确的真实时长——每次唤醒都可能对齐到帧边界,误差会一圈圈累积,对时长敏感的玩法要留出容差。
想让一段 Verse 驱动的移动动画尽可能平滑,循环里应该写哪个?
来源
本文整理自 Epic 开发者论坛的社区实测: