我正在尝试使用HTML5画布制作一个简单的迷宫导航游戏,其中玩家角色停留在屏幕的中心,迷宫围绕它们移动。
迷宫是使用二维瓷砖对象阵列来表示的,所以我的第一种绘图方法是:
for(var row=frameStart; row<=frameEnd; row++) {
for(var col=frameStart; col<=frameEnd; col++)
maze[row][col].draw();
}
}
...每次调用paint方法时都会计算frameStart和frameEnd,以避免绘制迷宫中不可见的部分。无论如何,这对我来说有点太慢,所以我决定使用
将整个迷宫保存到图像中var img = new Image();
img.src = canvas.toDataURL();
所以现在,我只画了一次迷宫,将它保存到图像中,然后使用ctx.drawImage(img, ...)
每帧只绘制一部分保存的图像,而不是循环遍历大量的图块元素并逐一绘制。
然而,我发现这种方法并没有明显加快,我对如何进一步提高性能感到茫然,我不知道为什么我的图像创意没有提高性能。它目前需要比我想渲染每个帧更长的时间。我目前已将游戏锁定为渲染每一帧,因此我可以根据玩家角色的移动速度轻松查看所需的时间。
答案 0 :(得分:3)
看着你的游戏,我发现它在我的机器上运行得很好。
使用requestAnimationFrame
但是看看你的代码,你已经以糟糕的方式实现了渲染。
您正在使用
将渲染器背靠背放置 ... cut from bottom of function reactToUserInput
if(needsRedraw) {
// Redraw canvas with interpolation
drawMaze(true, oldLocation, 0);
return; // drawMaze will call this function when it's done
}
}
drawMaze();
setTimeout(reactToUserInput, 0);
在函数reactToUserInput
并在drawMaze()
// Either continue interpolation, recall user input function, or stop entirely
if (interpolate) {
if (interpOffset.mag == 0) {
reactToUserInput();
} else {
setTimeout(function () {
drawMaze(true, oldUserLocation, recurseCount + 1);
}, 0);
}
return;
}
对于某些硬件设置,这可以避免减速。
使用requestAnimationFrame
每1/60秒渲染一次帧。这也将限制你在快速机器上的游戏,因为我看不到任何时间控制的运动。它还有助于在GPU GPU和GPU功耗较低的机器上进行渲染,从而更好地管理GPU状态,因为现在您在不需要时强制进行状态更改。
用户不会看到以60fps以上完成的任何渲染,因此请避免不必要的渲染。
为什么preRendering没有帮助
然后我在你的游戏上运行了一个配置文件,结果显示drawCell功能确实是瓶颈。 ctx.drawImage
中drawCell
的来电占您整体处理的32%。 (但是当你不断渲染时,这个值会产生误导)
渲染没有通过预渲染得到改善的原因是你要求太多的GPU。我看到的迷宫是150个瓷砖150个瓷砖,每个电池是65乘65像素。这使整个迷宫的尺寸9750乘以9750像素,消耗了与页面,操作系统和正在发生的任何其他过程共享的总共380.25MB的GPU内存。只有非常高端的机器才会乐意处理这么多的RAM,但其余部分将疯狂地从系统RAM中分页,导致速度减慢(由于持续渲染而复杂化)。
关于画布上图像大小的经验法则。切勿尝试使用大于设备最长分辨率4倍的图像。设备可以调整到显示器的分辨率,并可以很好地处理接近该分辨率的图像。重温一下,你就超越了硬件的功能。
如何修复并获得良好的帧速率。
观看游戏时,没有理由不在所有设备上以60fps的速度运行。
使用requestAnimationFrame为渲染和用户输入计时(没有人可以在超过60hz时切换一个键,你的游戏不需要立即反应)。
减少像素内存使用量。你的瓷砖是65乘65,这意味着在gpu中,直到图像在内存中占用128 x 128像素。搜索&#34;呈现两个&#34;的权力找出原因。
将单元格分辨率更改为64乘以64。
而不是预先渲染整个场景,而是创建一个屏幕外的画布,即播放区域大小加上2个单元格。因此,如果要绘制的单元格数量是32乘32,那么创建一个34乘34个单元格的画布。在第一次渲染时,将所有34乘34个单元格绘制到该画布上。然后绘制画布以跟随播放器,当游戏到达某个点并且沿着您要移动的边缘没有单元格时,将画布复制到自身上以便为行进方向上的新单元腾出空间,然后渲染缺失单元格的行或列。
// playfield is the offscreen canvas with .ctx as it context
playfield.ctx = playfield.getContext("2d");
// to move one cell up in the playfield
playfield.ctx.drawImage(playfield,
0, cellSize, playfield.width, playfield.height-cellSize,
0,0,playfield.width, playfield.height-cellSize
)
// then draw the missing bottom row of cells only
// then just draw the playfield to the onscreen canvas
ctx.drawImage(playfield, mazeLeft, mazeTop)
这将需要一些重写,但会让你的游戏运行得非常顺利,让你专注于游戏而不是表现。