在浏览器里造开放世界,第 2 部分:Worker 物理和输入延迟恐惧
作者:Oleg Sidorkin,Cinevva CTO 和联合创始人
刚看到?看系列导览。那里解释了 spike 是什么,并链接了所有部分。
只要你做浏览器多人玩到一定时间,你最终会碰上这个争论。
"物理放 worker 里架构干净。物理放主线程感觉更稳。"
两种说法都可能成立。真正重要的是真实输入下的操作手感和延迟。
Spike 2 就是用来给这个问题一个测量出的答案,而不是观点。
我们复用了 Spike 1 的地形,把 Rapier 集成到一个专门的 module worker 里。每帧把输入状态发给 worker,仿真在那边推一步,权威位置返回给渲染端。
关键指标是输入到可见运动的延迟,以及物理步进的耗时。我们还在常规移动、冲刺爆发和跳跃节奏下盯着抖动。
结果比预期好。在我们这套消息形状和节奏下,worker 边界并没主导延迟。操作仍然感觉立即响应,而这是玩家唯一会在意的事。
这个阶段的一个挑战是"解读风险"。一个成功的结果之后,团队经常会过度泛化,假设架构这个问题永远定下来了。其实没有。我们只验证了一个具体的场景和硬件画像。后面的 spike 在 GPU 和流式加载压力变了之后,还得重新检查这些假设。
这个 spike 也给了我们一次流程升级。我们开始让交互式 spike 默认在 HUD 上暴露计时遥测。团队的讨论从"感觉不对"变成了"这条路径多花了 1.2 ms"。
第 3 部分讲那些不那么吸睛、但避免了后期昂贵意外的实验。广播负载、移动端约束、行为生成可靠性。
本章涉及的技术
Rapier。 一个用 Rust 写的物理引擎,编译成 WebAssembly 在浏览器里用。它处理刚体、collider、joint、character controller 和 raycasting,性能是原生的 2-3 倍。在开放世界里,Rapier 提供玩家 character controller(在地形上走、爬台阶、在斜坡上滑)、物体碰撞、用于互动的 raycasting,还有触发体积。看 Rapier 文档,以及我们关于物理的浏览器 3D 技术指南。
Web Workers。 在浏览器里跑 JavaScript(或 Wasm)的、独立于主线程的线程。物理仿真放 worker 里意味着一次重的 world.step() 调用不会卡住渲染。主线程每帧通过 postMessage 把输入状态发给 worker,再收到权威位置。延迟代价是两次消息跳(桌面端每次大约 0.1-0.5 ms)。好处是渲染线程永远不会因为碰撞检测而停顿。Transferable 对象(ArrayBuffer 转移)消除了大位置数组的拷贝开销。
WebAssembly (Wasm)。 一种二进制指令格式,在浏览器里以接近原生的速度运行。Rapier、Havok、Recast 全都编译成 Wasm。Rapier-Wasm 一次物理步进,几百个 body 通常是 0.5-2 ms,等价的 JavaScript 要 5-15 ms。Wasm 模块作为 .wasm 文件和 JavaScript 胶水代码一起拉下来。看 WebAssembly 规范。
输入到画面延迟。 按键到屏幕上对应视觉变化之间的时间。移动要感觉"立即",这个数得保持在大约 80 ms 以内。在 worker 物理设置里,链路是:keydown 事件(主线程)-> postMessage 到 worker -> 物理步进 -> postMessage 回来 -> 渲染器应用位置 -> 下一次 vsync。每一跳都加延迟,这就是为什么测量比架构图更重要。
12 篇中的第 2 篇。 上一篇:第 1 部分 - 我们从尝试把它弄崩开始 下一篇:第 3 部分 - 救了我们的那些不吸睛的 spike 系列导览:/zh-CN/blog/2026-02-25-open-world-browser-series-guide