优化 HTML5 canvas 游戏循环

Optimizing HTML5 canvas game loop

我目前正在制作一款 HTML5 游戏,我正在尝试在 canvas 上绘制各种内容。我的游戏基本上就是让你在无限区域内移动,但我不知道如何优化我的代码以在屏幕上绘制灌木丛。它工作正常,但它滞后了很多,我知道有办法优化它。这是我当前的代码:

    for(var x=offset[0];x<(offset[0]+canvas.width)+300;x++) {
        for(var y=offset[1];y<(offset[1]+canvas.height)+300;y++) {
            if(x % 85 == 0 && y % 85 == 0 && noise.simplex2(x, y) == 0) {
                ctx.drawImage(treeimage, ((x-offset[0])*-1)+canvas.width, ((y-offset[1])*-1)+canvas.height);
            }
        }
    }

treeimage 定义如下:

var treeimage = new Image(); treeimage.src = 'images/mapobjects/tree2.png';

offset[] 是一个数组,其值分别是对象相对于玩家的水平和垂直偏移量(因此当玩家向左移动时,它会上升)。我使用单纯形噪声来生成灌木丛,因为我喜欢它们呈小块状。使 FPS 如此低的问题是,在我的屏幕分辨率下,我是 运行 2 模函数每帧 2137104,并且在更高的分辨率下变得更糟。我试图通过遍历我游戏的每个图块而不是每个像素来使其更快(每个图块是 85x85,因此将 y 和 x 递增 85 而不是 1)然后添加玩家偏移 % 85,但我对此有疑问跳来跳去,因为当它跳到下一个图块时,偏移量 % 85 没有立即变为 0,我尝试并尝试以多种不同的方式让它工作,但这是我让它工作的唯一方法。这就是它的外观,除了代码超慢之外,一切正常。 当我尝试优化它时,我是否遗漏了什么,或者是否有一种完全不同的方法可以修复它。我从来没有真正优化过代码,所以这对我来说是一件新鲜事。我可以看出所有延迟都来自这段代码,因为如果没有它,只需增加 85,它就可以正常工作。谢谢!

许多年前,由于计算机没有今天那么快,您必须进行一些繁重的数学运算,例如计算正弦或余弦 - 甚至模数 - 只有一个选择:

与其每次需要时都计算它,不如计算一次并将其存储在一个巨大的查找中 table。查找值当然比计算快得多。

所以在你的情况下,我建议为 x 和 y 的模生成两个数组

let xModulo = [];
let yModulo = [];
for (let a = 0; a < canvas.width; a++) {
  xModulo.push(a % 85);
}
for (let a = 0; a < canvas.height; a++) {
  yModulo.push(a % 85);
}

并在您的渲染循环中查找数组中的值,例如:

if (xModulo[x] == 0 && yModulo[y] == 0 && noise.simplex2(x, y) == 0) {
  ctx.drawImage(treeimage, ((x - offset[0]) * -1) + canvas.width, ((y - offset[1]) * -1) + canvas.height);
}

这应该会显着提升性能。根据您的需要,您可能需要将 canvas.width / canvas.height 更改为更高的值。 您甚至可以考虑为单纯形噪声生成查找 table。

每张图片 7225 次无意义操作

条件使代码变慢。尽可能避免使用它们。

例如这条线...

if(x % 85 == 0 && y % 85 == 0 && noise.simplex2(x, y) == 0) {

... 意味着您正在为每少于一棵树评估 if 语句 85 * 85 (7225) 次,这是大量不必要的开销。

  • 删除那 7224 次无用的迭代。

  • 尽可能避免索引数组,方法是将重复的数组查找存储在变量中。

  • 简化你的数学。例如((x-offset[0])*-1)+canvas.width可以简化为 canvas.width - x + offset[0].

  • 尽可能多地卸载到 GPU。默认情况下,所有位置计算都是通过在 GPU 上完成的变换进行的,因此上述数学运算可以在循环之前完成一次。

  • 性能的一般规则,通过将可能的代码移到循环外来减少循环内的代码量。

下面的代码片段实现了以上几点。

由于您没有提供有关偏移范围和 canvas 大小的详细信息,因此可以进一步优化以下代码

    var x, y;
    const STEP = 85;
    const offsetX = offset[0];
    const offsetY = offset[1];
    const startX =  Math.floor(offsetX / STEP) * STEP;
    const startY = Math.floor(offsetY / STEP) * STEP;
    const endX = startX + canvas.width;
    const endY = startY + canvas.height;
    ctx.setTransform(1, 0, 0, 1, canvas.width - offsetX, canvas.height - offsetY);
    
    for (x = startX; x < endX; x += STEP) {
        for (y = startY; y < endY; y += STEP) {
            if (noise.simplex2(x, y) == 0) {
                ctx.drawImage(treeimage, x, y);
            }
        }
    }
    // reset transform
    ctx.setTransform(1, 0, 0, 1, 0, 0);

考虑 quad tree

我怀疑对 simplex2 的调用会很慢。我看到的所有实现都做得很差。由于单纯形法的结果对于任何坐标都是恒定的,因此在游戏开始之前每个坐标只应执行一次(在生产代码之外)。

因为你想要一个无限(类似)的游戏场(无限是不可能的),所以 RAM 要求太大了。我没有太多建议,除了......放弃无限并为运动场大小设置一个实际限制,这将允许你创建一个可以让它飞行的quad tree地图。