6

我正在开发一个模拟万有引力的 javascript 游戏。它使用 HTML5 画布元素为行星绘制 2D 椭圆。我在 Google Chrome 中测试我的游戏。这是游戏的链接: http://gravitygame.hostingsiteforfree.com/index.php?page= playHTML

直到 5 月 24 日,它工作得很好。但是,Chrome 从 26.0.1410.64 升级到 27.0.1453.94 后,填充的椭圆有时不会绘制出来。每次我加载我的游戏时都不会发生这种情况,而且我从来没有在本地运行时让它中断。

这是游戏运行的屏幕截图:在此处输入图像描述

这是一个屏幕截图,显示它没有填充椭圆:在此处输入图像描述

我不知道发生了什么。我将包括绘制所有行星的循环部分。为了便于阅读,我对其进行了修改。

    var i = bodies.length;
    while(i--){
    var I = bodies[i];
    var planetRad = (I.width/2)*_scale;
    if(_showTrails){
        //draw the planet's trail
    }
    if(//the planet is completely off the screen){
        //draw a red planet on the edge of the screen
        ctx.beginPath();
        ctx.arc(nX, nY, 2.5, 0, TWOPI);
        ctx.fillStyle = offScreenColor;
        ctx.fill();
        ctx.strokeStyle = offScreenOutline;
        ctx.stroke();
    }
    else{
        //draw planet
        ctx.beginPath();
        ctx.arc(nX, nY, (I.width/2)*_scale, 0, TWOPI);
        ctx.closePath();
        ctx.fillStyle = I.bodyColor;
        ctx.fill();     
    }
    if(_showMotionVector){
        //draw a line from the center of a planet showing the direction and speed it's travelling
        ctx.strokeStyle = motionColor;
        ctx.beginPath();
        ctx.moveTo(I.getScX(), I.getScY());
        ctx.lineTo(I.motion.x * _scale * 12 + I.getScX(), I.motion.y * _scale * 12 + I.getScY());
        ctx.stroke();
    }
}

为什么偶尔会突然坏掉?

4

1 回答 1

5

我查看了您的在线代码,发现您正在使用setInterval动画循环。

这很可能是代码无法完成调用计算等的原因。您冒着堆叠调用的风险 - 对于上下文,这意味着您可以拥有相互重置的路径。

首先尝试替换setIntervalsetTimeout. 您当然需要从代码中再次重新触发它 - 更好的是,将所有内容放在一个函数中,并在该函数的末尾设置一个 setTimeout,即:

function animate() {
    //... calcs and redraws which you have in setInterval
    setTimeout(animate, 0);
}
animate();

我在此测试中使用 0 表示超时。setTimeout/setInterval在任何情况下都不会同步到屏幕刷新率。

如果这有效,那么您就知道原因了。下一步是用 替换它requestAnimationFrame,但让我知道它是怎么回事。

为了说明这个问题,我们可以看这个插图:

工作间隔

每个块代表循环内的一个功能,一个循环是一种颜色。请记住,setInterval固定间隔调用,而setTimeout调用对于调用时间。在此示例中,功能在时间预算内执行,因此一切顺利。

在下一个插图中:

堆叠间隔

支出超出预算,因此setInterval再次调用并在第一个循环完成之前将下一次调用排队到第二个循环。在调用之间处理队列时,您最终可能会冒两个函数在“同时”处理上下文的风险(或者以与您预期不同的顺序出现)。

Javascript 当然是单线程的,所以它们不会同时执行,但是会等待 - 如果在最后一个块有时间调用之前调用下一个队列的第一个块,那么第一个块将修改上下文甚至可能在调用上一个调用的最后一个调用之前更改路径。随着时间的推移,滞后会增加,并且可能(除非一些额外的可用处理资源不时地解决队列 - 在繁忙的系统上这不太可能发生)随着更多堆叠的发生而变得越来越糟。

即,在这种情况下,您可以在填充beginPath()之前将行添加到上下文中。arc

(希望这有任何意义......)

UsingsetTimeout将防止这种情况发生,因为它不会在动画循环中的所有调用返回之前执行。更好的选择是使用requestAnimationFrame,因为这将在可能的情况下与屏幕刷新率同步调用。它更底层,因此也更有效。

另一种方法(不是双关语)是使用网络工作者进行计算。这将是多线程的,并且可以提高整体性能,因为 web-worker 不会影响 UI 线程。

于 2013-05-28T21:33:23.403 回答