因此,我遇到的问题是,在开发HTML5 canvas应用程序时,我需要使用大量的转换(即平移,旋转,缩放),因此需要对context.save()和context.restore进行大量调用()。即使绘制很少,性能也会很快下降(因为在循环中,多次调用了save()和restore())。除了使用这些方法之外,还有其他方法可以使用转换吗?谢谢!
避免保存还原
使用setTransform,因为这样就不需要保存和还原。
保存还原的原因有很多,它们会减慢速度,这取决于当前的GPU && 2D上下文状态。如果您将当前的填充和/或笔触样式设置为大图案,或者具有复杂的字体/渐变,或者使用的是过滤器(如果有),则保存和还原过程可能比渲染图像花费更长的时间。
在编写动画和游戏时,性能就是一切,对我而言,它是关于精灵数的。我可以在每帧(第60秒)中绘制的精灵越多,可以添加的FX越多,环境越详细,游戏效果越好。
我将状态保持开放状态,也就是说,我没有详细跟踪当前2D上下文状态。这样,我永远不必使用保存和还原。
ctx.setTransform而不是ctx.transform
因为变换功能变换,旋转,缩放,翻译乘以当前变换,所以很少使用它们,因为我不知道变换状态是什么。
为了处理未知数,我使用setTransform来完全替换当前的转换矩阵。这也使我可以在一次呼叫中设置比例和平移,而无需知道当前状态是什么。
ctx.setTransform(scaleX,0,0,scaleY,posX,posY); // scale and translate in one call
我还可以添加旋转,但是用于查找x,y轴向量(setTransform中的前4个数字)的javascript代码比旋转慢。
精灵并渲染它们
以下是扩展的Sprite函数。它从一个精灵表中绘制一个精灵,该精灵具有x&y比例,位置和中心,并且由于我总是使用alpha,所以也要设置alpha
// image is the image. Must have an array of sprites
// image.sprites = [{x:0,y:0,w:10,h:10},{x:20,y:0,w:30,h:40},....]
// where the position and size of each sprite is kept
// spriteInd is the index of the sprite
// x,y position on sprite center
// cx,cy location of sprite center (I also have that in the sprite list for some situations)
// sx,sy x and y scales
// r rotation in radians
// a alpha value
function drawSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}
在普通的机器上,您可以使用该功能以全帧速率渲染1000个+子画面。在Firefox上(在撰写本文时),该函数的功能为2000+(小精灵是从1024 x 2048的小精灵表中随机选择的小精灵),最大小精灵大小为256 * 256
但是我有15种以上的此类功能,每个功能都具有执行我想要的功能所需的最少功能。如果它从未旋转过或缩放过(即针对UI),则
function drawSprite(image, spriteInd, x, y, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(1,0,0,1,x,y); // set scale and position
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,0,0,w,h); // render the subimage
}
或最简单的游戏精灵,粒子,子弹等
function drawSprite(image, spriteInd, x, y,s,r,a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
ctx.setTransform(s,0,0,s,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a;
ctx.drawImage(image,spr.x,spr.y,w,h,-w/2,-h/2,w,h); // render the subimage
}
如果是背景图片
function drawSprite(image){
var s = Math.max(image.width / canvasWidth, image.height / canvasHeight); // canvasWidth and height are globals
ctx.setTransform(s,0,0,s,0,0); // set scale and position
ctx.globalAlpha = 1;
ctx.drawImage(image,0,0); // render the subimage
}
通常可以缩放,平移和旋转运动场。为此,我保持了一个闭合转换状态(上面的所有全局变量都在变量和部分渲染对象上闭合)
// all coords are relative to the global transfrom
function drawGlobalSprite(image, spriteInd, x, y, cx, cy, sx, sy, r, a){
var spr = image.sprites[spriteInd];
var w = spr.w;
var h = spr.h;
// m1 to m6 are the global transform
ctx.setTransform(m1,m2,m3,m4,m5,m6); // set playfield
ctx.transform(sx,0,0,sy,x,y); // set scale and position
ctx.rotate(r);
ctx.globalAlpha = a * globalAlpha; (a real global alpha)
ctx.drawImage(image,spr.x,spr.y,w,h,-cx,-cy,w,h); // render the subimage
}
以上所有内容与实际游戏精灵渲染的速度一样快。
一般提示
切勿使用任何矢量类型渲染方法(除非有空闲的帧时间),例如fill,stroke,filltext,arc,rect,moveTo,lineTo,因为它们会立即变慢。如果需要渲染文本,请创建一个屏幕外的画布,对其进行一次渲染,然后显示为sprite或图像。
图像大小和GPU RAM
创建内容时,请始终对图像大小使用幂规则。GPU处理大小为2的幂的图像(2,4,8,16,32,64,128 ....),因此宽度和高度必须为2的幂。即1024 x 512或2048 x 128是合适的尺寸。
当您不使用这些大小时,2D上下文将不在乎,它所做的是扩展图像以适合最接近的功率。因此,如果我有300 x 300的图像以适合GPU上的图像,则必须将图像扩展到最接近的功效,即512 x512。因此,实际的内存占用量是您能够容纳的像素的2.5倍以上显示。当GPU的本地内存用完时,它将开始从主板RAM切换内存,这时您的帧速率将下降至无法使用。
确保调整图像大小,以免浪费RAM,这意味着您可以在碰到RAM壁之前将更多的东西装入游戏中(对于较小的设备来说,这根本就不多)。
GC是主要框架
最后一项优化是确保GC(垃圾收集器)几乎没有干什么。在主循环中,避免使用new(重用和对象而不是对其进行取消引用并创建另一个对象),避免从数组中推入和弹出(保持其长度不落)保持活动项的单独计数。创建一个自定义的迭代器,并推送对项目上下文敏感的函数(知道数组项目是否处于活动状态)。推送时,除非没有不活动的项目,否则不要推送新项目;当一个项目变为不活动状态时,请将其保留在数组中,并在以后需要时使用它。
我称之为“快速堆栈”的简单策略超出了此答案的范围,但可以处理零负荷(零寿命)的零负载(短期)游戏对象。一些更好的游戏引擎使用类似的方法(提供不活动项目池的池数组)。
GC应该少于您游戏活动的5%,否则,您需要找到不必要创建和取消引用的位置。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句