HTML5 GAME TUTORIAL(四): Create a proper game loop(译)
原文地址:Create a proper game loop
在JavaScript中创建适当的游戏循环,并了解帧频。测量并显示fps,以查看循环效果。请求动画帧,并在本HTML5游戏教程结束之前运行自己的循环。
为什么你需要一个游戏循环
在上一教程中,您已经创建了一个在HTML5画布上绘制矩形的应用程序。 一切都非常好,但是绘制操作仅执行一次。如果要使其看起来好像矩形正在移动,并最终创建一个实际的游戏,则需要绘制更多内容。多很多。
为此,您需要一个循环来重复执行游戏逻辑。在游戏世界中,此循环称为游戏循环。这将是您游戏的核心,并触发绘图操作。您将不再绘制单个静态图像,但是许多图像将相互绘制,从而产生运动。
这是一个带有移动圆的示例。它连续显示五个框架,每个框架的圆圈位置略有不同。这五个框架类似于游戏循环逻辑的五个迭代。 一步一步地快速移动圆形,可以产生运动效果。
高帧频的好处
您可能知道,在游戏中,人们谈论他们的游戏装备可以达到多少fps。Fps代表每秒的帧数。 每次在屏幕上绘制游戏对象时,都将算作一个帧。
Fps是每秒可以在屏幕上绘制游戏的时间。通常,硬件越好,fps越高。fps越高,游戏将越流畅。同样,帧之间的延迟将变得更小,从而使游戏的状态更快。 这为您提供了游戏方面的优势。这就是为什么专业游戏玩家想要挤出他们能得到的每一帧并在最佳硬件上花费很多的原因。
游戏循环的理想帧速率
每秒钟人类可以处理10到12帧。 超出此限制的帧数,您就会将其视为一种运动,这就是您想要的游戏。 当然,以12fps的速度运行的游戏非常不稳定。您可能会认为它是一种动作,但是如果要使其看起来更平滑,则需要更多帧。电影通常使用24fps,但大多数游戏的帧数甚至更多。如果有更好的选择,为什么不仅仅以120fps的速度运行游戏并使它变得更加流畅呢?
当然,您还需要考虑显示器的刷新速率。在60Hz的显示屏上(每秒60次刷新率)以120fps的速度运行游戏不会有什么好处。您将要绘制的帧超出了显示屏的处理能力。多余的帧将永远不会显示,只会浪费您的系统资源。现代显示器支持更高的刷新率,因此如果您想每秒充分利用所有120帧,则需要有一个这样的显示器。 但是HTML5游戏不仅适用于具有显示效果出色的高端台式机,而且还必须在移动设备上运行并且能表现良好。
理想的情况是帧率等于显示器刷新率的fps。它将提供最流畅的体验,并且游戏状态更新也是最快的。同时,您希望限制最大fps,以防止游戏占用过多的系统资源,并成为移动设备上的耗电设备。
因此,总而言之,这是在创建HTML5游戏时要在游戏循环中需要考虑的内容:
- 尽可能高的帧率以获得平滑度
- 帧速率不高于屏幕刷新率
- 帧速率不占用过多的系统资源
让我们检查一些选项,看看它们满足这些要求的程度。您将找到适合您游戏的完美循环,并了解常见错误。
创建游戏循环
好的,所以您需要游戏循环吗?为什么不只在JavaScript代码中使用这个简单的while循环?
// A bad game loop
while (running) {
draw();
}
循环会启动并无限期地绘制,直到有人告诉它停止运行(例如暂停游戏时)。因此,您将从系统中获得最大的潜力,并达到硬件允许的最大fps。
但是有一个小问题。每个浏览器选项卡上的JavaScript都在一个线程上运行。通过while循环,可用系统资源的每一点将一次又一次地执行绘制操作。这将使您的浏览器无法执行其他任务,例如管理用户输入或其他重要的事件。当您让它运行一段时间后,它将挂起您的浏览器,并最终发出著名的“此页面未响应”警告。
要解决此问题,您需要在执行游戏循环时让浏览器喘气。您可以使用setInterval()之类的方法为每个帧循环设置的时间。这将在每个绘图操作之间花费一些时间,从而为系统提供了执行绘图以外的其他任务的空间。
// Another bad game loop
setInterval(gameLoop, 16);
function gameLoop() {
draw();
}
以16ms的间隔,此游戏循环将达到约60fps。这将比while循环执行得更好,但是不能保证当gameLoop()函数被触发时浏览器已准备好执行重新绘制。您可能会计算出永远不会显示在显示器上的帧。如何解决这个问题?
JavaScript正确的游戏循环
您需要一种在循环时让浏览器有空的方法,并使游戏循环与浏览器重画同步。 幸运的是有一个解决方案。您可以使用window.requestAnimationFrame()告诉浏览器您要请求动画或游戏的重绘。
// The proper game loop
window.requestAnimationFrame(gameLoop);
function gameLoop() {
draw();
window.requestAnimationFrame(gameLoop);
}
浏览器将自行执行回调函数。意味着它不会挂起系统。另外,当浏览器标签不再集中时,浏览器可能会减少回调的数量,以减少系统加载。例如,这将提高运行该游戏的设备的电池寿命。
您还记得有关帧频的那一段吗?好吧,浏览器会为正在运行的设备选择合适的fps。这通常意味着您的游戏循环将以60fps的速度运行,但通常会与您的显示刷新率匹配。requestAnimationFrame()函数负责此工作。
该方法满足适当的游戏循环的所有要求:循环以帧速率运行,这将使游戏看起来更流畅,考虑屏幕的刷新率并减少系统资源的使用。 这是用于您自己的HTML5游戏的完美匹配。
如何使用requestAnimationFrame()?
函数window.requestAnimationFrame()接受回调函数作为参数。您可以在其中传递gameLoop()函数。当浏览器准备就绪时,它将执行该函数。
您将必须通过一次调用window.requestAnimationFrame()来启动游戏循环,然后继续在循环内调用它。在下一个示例中更容易理解:
<script>
"use strict";
let canvas;
let context;
window.onload = init;
function init(){
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
// Start the first frame request
window.requestAnimationFrame(gameLoop);
}
function gameLoop(timeStamp){
draw();
// Keep requesting new frames
window.requestAnimationFrame(gameLoop);
}
function draw(){
let randomColor = Math.random() > 0.5? '#ff8080' : '#0099b0';
context.fillStyle = randomColor;
context.fillRect(100, 50, 200, 175);
}
</script>
来自requestAnimationFrame()的回调具有一个参数,它是包含当前时间的时间戳。它的值与调用Performance.now()所获得的值相同,但是以这种可选的方式提供。您可以从gameLoop()函数访问timestamp。
您现在不会用到时间戳。在下一个有关画布动画的教程中,您将学到更多关于将时间用作游戏循环因素的信息。当您有移动的物体时,解释那里的东西会更容易。 只要记住您以后可以使用它,它对于计算运动很重要。
查看正在运行的游戏循环
让我们尝试一下您的新游戏循环。运行此代码时,与上一教程相比,您仅需调用一次draw()函数即可看到很大的不同。现在连续多次调用draw()。矩形大约每秒重绘60次,具体取决于运行循环的设备。
这是您的游戏循环的示意图。随着本教程系列的进展,它将扩展更多任务。
矩形的颜色仍然是随机的。因此,每帧都会选择一种新的随机颜色。这就是为什么您现在看到一个闪烁的矩形在红色和蓝色之间快速切换的原因。这样可以更轻松地查看游戏循环是否正在正常工作(但看起来并不愉快)。
计算并显示fps
做得好,您已经制作了一个游戏循环,并且可以正常工作。但是,如果您可以衡量其性能并检查每秒产生多少帧,那不是很好吗?让我们尝试使用游戏循环中提供的timestamp参数来计算当前fps。
首先通过计算从上一帧开始经过的时间开始。您可以通过从当前时间戳中减去前一帧的时间戳来实现。剩下的时间是自上一帧以来的毫秒数。
将该数字除以1000,可以从毫秒到秒。花费一秒钟的时间即可生成一帧。用1除以该数字即可得到每秒的帧数。
这是一个使用fillText()绘图操作的非常简单的实现示例,就像在画布绘图教程处理一样,将fps作为文本绘制到屏幕上:
let secondsPassed;
let oldTimeStamp;
let fps;
function gameLoop(timeStamp) {
// Calculate the number of seconds passed since the last frame
secondsPassed = (timeStamp - oldTimeStamp) / 1000;
oldTimeStamp = timeStamp;
// Calculate fps
fps = Math.round(1 / secondsPassed);
// Draw number to the screen
context.fillStyle = 'white';
context.fillRect(0, 0, 200, 100);
context.font = '25px Arial';
context.fillStyle = 'black';
context.fillText("FPS: " + fps, 10, 30);
// Perform the drawing operation
draw();
// The loop function has reached it's end. Keep requesting new frames
window.requestAnimationFrame(gameLoop);
}
您可以运行代码来检查fps计,并查看游戏循环是否正常运行。如果您在台式机设备上查看此页面,则该页面每秒应能命中60帧。
fps每帧更新一次,因此可能视觉体验不好,但对于本演示很好。如果愿意,您可以花更多时间,并考虑一些使fps更新频率降低的方法。例如,您可以缓冲fps,并且每秒仅更新一次。但是目前,这一切都在fps计上。
下一步是什么?
现在所有这些都在游戏循环中。您的JavaScript游戏循环就位,并且矩形每秒重绘多次。您了解fps和游戏循环之间的关系,并且可以测量自己游戏的fps。如果您有任何疑问,请随时在评论部分提问。
在本教程的下一步中,矩形将更加生动。您将学习如何在HTML5画布上添加运动并制作动画。此外,您还将了解帧频对动画的影响。