# 跨显示器保持可预测的游戏循环

> 构建浏览器游戏更新并围绕动画帧进行绘制，而不将模拟速度与玩家的显示刷新率联系起来。

- URL: https://oneshotgems.ai/zh-Hans/topics/browser-game-loop
- 类别: 游戏开发者指南
- 类型: 指南
- 更新日期: 2026年10月5日

浏览器游戏循环负责协调模拟、绘制和输入，但不能假设每个显示器每秒都会产生相同数量的帧。回调可能按不同的刷新率运行，在标签页隐藏时暂停，或在主线程繁忙时延迟执行。让模拟基于经过的时间，并由浏览器安排画面呈现。这样更容易理解运动，也能明确控制长时间卡顿后的行为。

## 将状态变化与绘图分开

使用 requestAnimationFrame 请求下一次画面更新，并把它提供的时间戳传给一个简洁的循环协调器。根据经过的时间更新位置、计时器和规则，然后绘制当前状态。渲染器不应负责判断是否发生碰撞，碰撞检测也不应取决于精灵是否恰好被绘制出来。

做一个简单的平台移动测试：让标记以选定的世界单位/秒水平移动，分别在 60 Hz 显示器和高刷新率显示器上运行同一路径。如果在相同的实际时间内，标记在某个显示器上移动得更远，说明仍有部分移动量是按每次回调计算的，而非按时间计算。

## 选择延迟帧的恢复策略

长时间暂停会产生很大的时间间隔。对于轻量的实时游戏，应限制这段时间，避免玩家回来时看到角色瞬间穿过多个障碍。对于确定性模拟，应使用带累加器的固定更新步长，限制追赶更新的次数；如果插值有帮助，可以在模拟状态之间绘制画面。

在可重复的障碍路线中记录最长的回调间隔，然后切换到其他页面再返回，检查游戏状态。明确决定游戏应暂停、少量追赶时间，还是从上一个状态继续；不要让一次延迟的回调替你做决定。

## 常见问题

**我应该使用 setInterval 进行绘图吗？** 使用 requestAnimationFrame 进行视觉工作，因为它安排在重绘附近，并且通常会因隐藏文档而暂停。计时器仍然可以安排非视觉任务，但不应将其视为显示时钟。

**帧时间戳从何而来？** requestAnimationFrame 回调提供高分辨率时间戳。 MDN 记录了其时间安排和一次性行为；使用该时间戳来计算进度而不是计算回调。

## 跟随好奇心探索

- [使用增量时间，避免运动跳变](https://oneshotgems.ai/zh-Hans/topics/delta-time-in-games): 将经过的时间应用于游戏运动，选择安全的追赶策略，并在不同的刷新率和停顿后测试运动。
- [隐藏选项卡时暂停游戏](https://oneshotgems.ai/zh-Hans/topics/pausing-background-games): 使用页面可见性更改来选择暂停哪些内容、保存哪些内容以及如何在不发生时间跳跃的情况下恢复浏览器游戏。
- [保持浏览器游戏 HUD 同步](https://oneshotgems.ai/zh-Hans/topics/synchronizing-game-hud): 通过将游戏状态视为事实来源并从一个更新路径渲染 HUD 值，保持分数、生命和计时器的一致性。

## 延伸阅读

- [MDN：requestAnimationFrame()](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame)

---
规范页面: https://oneshotgems.ai/zh-Hans/topics/browser-game-loop
