游戏循环使我的浏览器崩溃

Game loop crashing my browser

本文关键字:浏览器 崩溃 我的 循环 游戏      更新时间:2023-09-26

我正在尝试编写我的第一个html5游戏。但是,游戏循环导致我的浏览器变得无响应(最终被浏览器关闭(。我创建了一个状态机:

while(state != State.EXIT){
  switch(state){
    case State.SPLASH:
        break;
    case State.HOW_TO:
        break;
    case State.PLAY:
        oldTime=Date.now();
        state=gameLoop();
        break;
    case State.GAME_OVER:
        break;
    default:
        state=State.EXIT;
    }
}

这似乎工作正常。那么,这里是游戏循环:

function gameLoop(){
var newTime=Date.now();
var delta=newTime-oldTime;
update(delta/1000); 
render();
oldTime=newTime;
return state;
}

这是崩溃发生的地方。如果我取出 return 语句,它会返回 null 或任何 javascript 返回的内容。而且,这很好。它运行一次并退出。但是,如果我把它留在那里,这就是浏览器抓住的地方。更新功能使我的角色能够移动,渲染功能将一张图像绘制到屏幕上。非常简单的东西。

注意:如果这很重要,这是在画布元素中编写的。

溶液!我创建了一个 stateSelector(( 函数,其中包含上面的 switch 语句(没有 while(。但是,我没有使用 state=gameLoop,而是使用了 interval=setInterval(gameLoop, 1(。然后,当我想停止时,我使用 clearInterval(interval(,紧跟 stateSelector((。显然,如果我想更改状态,我会在调用 stateSelector 函数之前执行此操作。我可能会让它接受一个包含我想要进入的状态的参数,但这是一个我可以稍后评估的小更改。我只是想宣布我的解决方案,以防其他人遇到这种情况。

JavaScript 是单线程的,在所有常见的浏览器环境中(实际上(在 GUI 线程中运行。当你是JavaScript时,浏览器的UI在JavaScript完成运行之前不会更新。

您正在使用永远不会完成的while循环,因此 UI 永远不会更新。要解决此问题,您需要稍微重组一下:渲染一个帧,然后告诉浏览器您想尽快渲染另一个帧;它可以更新 UI 并执行其他浏览器操作,然后它可以返回您以呈现另一个帧。

实现

有一个名为requestAnimationFrame的实验性新功能可以做到这一点。由于它仍处于实验阶段,因此要使用它,您需要检查它的浏览器特定版本,或者如果它根本不可用,请提供回退。以下是特定于浏览器的版本的一些名称:

  • 壁虎(火狐(的mozRequestAnimationFrame
  • WebKit webkitRequestAnimationFrame(Chrome 和 Safari(
  • msRequestAnimationFrame for Trident (Internet Explorer(

因此,如果有无前缀的requestAnimationFrame可用,请使用该。如果它不可用,但前缀可用,请使用它。如果这些都不起作用,您可以使用回退:

function fallbackRequestAnimationFrame(func) {
    setTimeout(func, 10); // Schedule func to be run in 10 milliseconds.
}

以下是在MDN上找到的代码的略微修改版本:

var myRequestAnimationFrame =
       window.requestAnimationFrame
    || window.mozRequestAnimationFrame
    || window.webkitRequestAnimationFrame
    || window.msRequestAnimationFrame
    || fallbackRequestAnimationFrame;

一旦你弄清楚了可以使用哪个requestAnimationFrame函数,你可以将你的游戏循环(似乎不是没有循环的gameLoop函数,而是while循环(更改为如下所示:

function runFrame() {
    switch(state) {
        // state handling code
    }
    if(state != State.EXIT) {
        myRequestAnimationFrame(runFrame);
    }
}

然后启动它:

runFrame();

我认为您可能需要某种暂停,如果您在没有暂停的情况下循环,它将一遍又一遍地消耗处理循环的所有 CPU,从而阻止页面呈现。

JavaScript 运行在浏览器用于呈现页面的同一线程上,因此如果您编写无限循环,浏览器永远不会获得刷新页面的控制权。(现代浏览器会检测"长时间运行"的循环,并为用户提供中止它们的机会,但这对游戏没有帮助。

您需要使用setTimeout()setInterval(),并在以下方面有一些变化:

function gameLoop() {
   // do calculations
   // render
   // etc
   if (!gameOver)
       setTimeout(gameLoop, 30);
}
gameLoop();

(注意:你原来的gameLoop()函数实际上并没有循环 - 你的循环是在函数外部控制的 - 而我刚刚展示的确实循环

setTimeout()函数将函数排队等待稍后运行,然后立即继续下一行代码。当当前代码完成执行时,浏览器将重新获得控制权以更新显示等。然后在(大约(指定的间隔(以毫秒为单位(之后执行排队的函数。

上面的效果类似于递归调用,其中函数直接调用自身,除了同时使用 setTimeout() 将控制权交还给浏览器。

gameLoop()函数之外,您可以为键和/或鼠标事件定义事件处理程序,并让gameLoop()使用这些更新变量来决定如何移动玩家的角色,就像我在回答另一个问题时所说的那样。

通常,程序员通过添加sleep(s)yield()调用来使循环发挥良好作用,但由于 JavaScript 的事件驱动模型缺少这些,因此您可以改为用一个setInterval()替换循环,该可以每隔指定的时间间隔(例如每 33 毫秒(调用包含类似循环体的函数,以获得 30 fps 的体验。