剖析游戏结构

剖析游戏结构

处理可变刷新率需求的其他方法

存在其他解决问题的方法。

一种常见的技术是以恒定的频率更新模拟,然后绘制尽可能多的(或尽可能少的)实际帧。更新方法可以继续循环,而不用考虑用户看到的内容。绘图方法可以查看最后的更新以及发生的时间。由于绘制知道何时表示,以及上次更新的模拟时间,它可以预测为用户绘制一个合理的框架。这是否比官方更新循环更频繁(甚至更不频繁)无关紧要。更新方法设置检查点,并且像系统允许的那样频繁地,渲染方法画出周围的时间。在 Web 标准中分离更新有很多种方法:

在 requestAnimationFrame() 中绘制,并在 setInterval() 或 setTimeout() 中更新。

即使在未聚焦或最小化的情况下,使用处理器时间,也可能是主线程,并且可能是传统游戏循环的工件(但是很简单)。

在 requestAnimationFrame() 中绘制,并在 Web Worker 的 setInterval() 或 setTimeout() 中对其进行更新。

这与上述相同,除了更新不会使主线程(主线程也没有)。这是一个更复杂的解决方案,并且对于简单更新可能会有太多的开销。

在 requestAnimationFrame() 中绘制,并使用它来戳一个包含更新方法的 Web Worker,其中包含要计算的刻度数(如果有的话)。

这个睡眠直到 requestAnimationFrame() 被调用并且不会污染主线程,加上你不依赖于老式的方法。再次,这比以前的两个选项更复杂一些,并且开始每个更新将被阻止,直到浏览器决定启动 rAF 回调。

这些方法中的每一种都有类似的权衡:

用户可以跳过渲染帧或根据其性能插入额外的帧。

你可以指望所有用户以相同的固定频率更新非修饰性的变量,而不会卡顿。

程序比我们前面看到的基本循环要复杂得多。

用户输入完全被忽略,直到下次更新(即使用户具有快速设备)。

强制插帧具有性能损失。

单独的更新和绘图方法可能类似于下面的示例。为了演示,该示例基于第三点,只是没有使用 Web Worker 以提高可读性(老实说,也包含可写性)。

警告:这个例子需要进行单独的技术审查。

js/*

* 以分号开头是为了以防此示例上方的代码行依赖于自动分号插入(ASI)。

* 浏览器可能会意外地认为整个示例从上一行继续。

* 如果前一行不为空或终止,则前面的分号标志着新行的开始。

*

* 我们还假设 MyGame 是以前定义的。

*

* MyGame.lastRender 跟踪上一次提供的 requestAnimationFrame 时间戳。

* MyGame.lastTick 跟踪上次更新时间。始终以 tickLength 递增。

* MyGame.tickLength 是游戏状态更新的频率。这里是 20 Hz(50ms)。

*

* timeSinceTick 是 requestAnimationFrame 回调和上一次更新之间的时间。

* numTicks 是这两个呈现帧之间应该发生的更新次数。

*

* render() 传入 tFrame, 因为 render 方法可能需要计算

* tFrame 距离最近的更新已经过去了多久,通过外推的方式

* 来获得场景数据。(对于快速设备,render 方法是纯装饰性的)。

* 用以绘制场景。

*

* update() 根据给定时间点计算游戏状态。通常需要用 tickLength

* 作为循环参数,递增更新。来保证游戏状态的严谨。传入 DOMHighResTimeStamp

* 代表当前时间。(除非需要增加暂停功能,传入的时间应该总是

* 上次更新时间 + 游戏的 tick 间隔。)

*

* setInitialState() 执行在运行主循环之前需要的任何任务。

* 它只是一个你可能添加的通用示例函数。

*/

;(() => {

function main(tFrame) {

MyGame.stopMain = window.requestAnimationFrame(main);

const nextTick = MyGame.lastTick + MyGame.tickLength;

let numTicks = 0;

// 如果 tFrame < nextTick,则需要更新 0 个 tick(对于 numTicks,默认为 0)。

// 如果 tFrame = nextTick,则需要更新 1 tick(等等)。

// 备注:正如我们在总结中提到的那样,你应该跟踪 numTicks 的大小。

// 如果它很大,要么你的游戏是卡住了,要么机器无法跟上。

if (tFrame > nextTick) {

const timeSinceTick = tFrame - MyGame.lastTick;

numTicks = Math.floor(timeSinceTick / MyGame.tickLength);

}

queueUpdates(numTicks);

render(tFrame);

MyGame.lastRender = tFrame;

}

function queueUpdates(numTicks) {

for (let i = 0; i < numTicks; i++) {

MyGame.lastTick += MyGame.tickLength; // 现在 lastTick 应是这一时间。

update(MyGame.lastTick);

}

}

MyGame.lastTick = performance.now();

MyGame.lastRender = MyGame.lastTick; // 假装第一次绘制是在第一次更新。

MyGame.tickLength = 50; // 这将使你的模拟运行在 20Hz(50ms)

setInitialState();

main(performance.now()); // 开始循环

})();

另一个选择是简单地做一些事情不那么频繁。如果你的更新循环的一部分难以计算但对时间不敏感,则可以考虑缩小其频率,理想情况下,在延长的时间段内将其扩展成块。这是一个隐含的例子,在火炮博物馆的炮兵游戏中,他们调整垃圾发生率来优化垃圾回收。显然,清理资源不是时间敏感的(特别是如果整理比垃圾本身更具破坏性)。

这也可能适用于你自己的一些任务。那些是当可用资源成为关注点时的好候选人。

相关推荐

兔子寓意是什么意思
beat365在线体育打不开

兔子寓意是什么意思

📅 10-21 👁️ 7279
你绝对不知道的呼吸灯手机推荐,最新3款超美设计!
365bet网站是多少

你绝对不知道的呼吸灯手机推荐,最新3款超美设计!

📅 06-28 👁️ 9232
欧文离奇重伤恐告别世界杯 昔日金童如此命运坎坷