65.9K
CodeProject 正在变化。 阅读更多。
Home

释放HTML5 Canvas的游戏潜力

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2012年6月8日

CPOL

8分钟阅读

viewsIcon

22085

HTML5 浏览器和适用于 Windows 8 metro 风格应用的 HTML5 现在是开发现代游戏的严肃候选者。

30 天开发一个 Windows 8 应用

HTML5 浏览器和适用于 Windows 8 metro 风格应用的 HTML5 现在是开发现代游戏的严肃候选者。

通过 Canvas,您可以访问一个硬件加速空间,您可以在其中绘制游戏内容,并且通过一些技巧,您将能够实现出色的60 帧每秒渲染。流畅性对于游戏至关重要,因为游戏越流畅,玩家的体验就越好。

本文旨在为您提供一些关于如何充分利用 HTML5 Canvas 的关键。这种效果主要受到我年轻时(80 年代)作为演示制作者编写的一些 Commodore AMIGA 代码的启发。

现在,它只使用了CanvasJavaScript(而原始代码仅基于 68000 汇编语言)。

<在此处插入 tunnel.zip 包,例如此页面>

完整代码可在以下网址获取:http://www.catuhe.com/msdn/canvas/tunnel.zip

本文的目的是解释如何从给定代码开始,对其进行优化以实现实时性能,而不是解释隧道是如何开发的。

使用离屏 Canvas 读取图像数据

我想谈论的第一个要点是如何使用 Canvas 来帮助您读取图像数据。确实,在每个游戏中,您都需要精灵或背景的图形。Canvas 有一个非常有用的方法来绘制图像:drawImage。此函数可用于在 Canvas 中绘制精灵,因为您可以定义源矩形和目标矩形。

但有时这还不够。例如,当您想对源图像应用某些效果时,它就不够了。或者当源图像不是简单的位图,而是游戏更复杂的资源时(例如,您需要从中读取数据的地图)。

在这些情况下,您需要访问图像的内部数据。但是Image标签无法读取其内容。这就是 Canvas 可以帮助您的地方!

确实,每次需要读取图片内容时,您都可以使用离屏 Canvas。这里的核心思想是加载一张图片,当图片加载完成后,您只需将其渲染到一个 Canvas 中(不包含在 DOM 中)。然后,您可以通过读取 Canvas 的像素(这非常简单)来获取源图像的每个像素。

此技术的代码如下(用于 2D 隧道效果读取隧道纹理数据)。

var loadTexture = function (name, then) {
    var texture = new Image();
    var textureData;
    var textureWidth;
    var textureHeight;
    var result = {};

    // on load
    texture.addEventListener('load', function () {
        var textureCanvas = document.createElement('canvas'); // off-screen canvas

        // Setting the canvas to right size
        textureCanvas.width = this.width; //<-- "this" is the image
        textureCanvas.height = this.height;

        result.width = this.width;
        result.height = this.height;

        var textureContext = textureCanvas.getContext('2d');
        textureContext.drawImage(this, 0, 0);

        result.data = textureContext.getImageData(0, 0, this.width, this.height).data;

        then();
    }, false);

    // Loading
    texture.src = name;

    return result;
};

要使用此代码,您必须考虑到纹理的加载是异步的,因此您必须使用then参数来传递一个函数以继续您的代码。

// Texture
var texture = loadTexture("soft.png", function () {
    // Launching the render
    QueueNewFrame();
});

使用硬件缩放功能

现代浏览器和 Windows 8 支持硬件加速的 Canvas。这意味着,例如,您可以使用GPU来缩放 Canvas 的内容。

在 2D 隧道效果的情况下,该算法需要处理 Canvas 的每个像素。因此,例如,对于 1024x768 的 Canvas,您需要处理 786432 个像素。为了流畅,您每秒需要执行 60 次,相当于每秒47185920个像素!

任何有助于您减少像素数量的解决方案都将大大提高整体性能,这一点显而易见。

再次,Canvas 有一个解决方案!以下代码向您展示了如何使用硬件加速将 Canvas 的内部工作缓冲区缩放到 DOM 对象的大小。

// Setting hardware scaling
canvas.width = 300;
canvas.style.width = window.innerWidth + 'px';
canvas.height = 200;
canvas.style.height = window.innerHeight + 'px';

值得注意的是 DOM 对象的大小(canvas.style.widthcanvas.style.height)与 Canvas 工作缓冲区的大小(canvas.widthcanvas.height)之间的区别。

当这两个尺寸之间存在差异时,硬件会用于缩放工作缓冲区,而在我们的例子中,这是一个很好的事情:我们可以处理较小的分辨率,让GPU将结果缩放到适合 DOM 对象(带有漂亮的免费滤镜来模糊结果)。

在这种情况下,渲染尺寸为 300x200,GPU 会将其缩放到窗口大小。

所有现代浏览器都广泛支持此功能,因此您可以依靠它。

优化您的渲染循环

在编写游戏时,您必须有一个渲染循环,您可以在其中绘制游戏的所有组件(背景、精灵、分数等)。此循环是您代码的骨干,必须进行过度优化,以确保您的游戏快速流畅。

RequestAnimationFrame

HTML 5 引入的一个有趣功能是window.requestAnimationFrame函数。而不是使用window.setInterval来创建定时器,该定时器每 (1000/16) 毫秒调用您的渲染循环(以实现良好的 60 fps),您可以使用requestAnimationFrame将此责任委托给浏览器。调用此方法表示您希望尽快由浏览器调用以更新与图形相关的内容。

浏览器会将您的请求包含在其自己的渲染计划中,并与浏览器的渲染和动画代码(CSS、过渡等)同步。此解决方案也很有趣,因为如果窗口未显示(最小化、完全被遮挡等),您的代码将不会被调用。

这有助于提高性能,因为浏览器可以优化并发渲染(例如,如果您的渲染循环太慢),并因此产生更流畅的动画。

代码非常明显(请注意供应商特定前缀的使用)。

var intervalID = -1;
var QueueNewFrame = function () {
    if (window.requestAnimationFrame)
        window.requestAnimationFrame(renderingLoop);
    else if (window.msRequestAnimationFrame)
        window.msRequestAnimationFrame(renderingLoop);
    else if (window.webkitRequestAnimationFrame)
        window.webkitRequestAnimationFrame(renderingLoop);
    else if (window.mozRequestAnimationFrame)
        window.mozRequestAnimationFrame(renderingLoop);
    else if (window.oRequestAnimationFrame)
        window.oRequestAnimationFrame(renderingLoop);
    else {
        QueueNewFrame = function () {
        };
        intervalID = window.setInterval(renderingLoop, 16.7);
    }
};

要使用此函数,您只需在渲染循环结束时调用它以注册下一帧。

var renderingLoop = function () {
    ...

    QueueNewFrame();
};

访问 DOM(文档对象模型)

为了优化您的渲染循环,您必须遵循至少一条黄金法则:不要访问 DOM。即使现代浏览器在这方面已经优化,但对于渲染循环来说,读取 DOM 对象属性仍然太慢。

例如,在我的代码中,我使用了 Internet Explorer 10 性能分析器(可在 F12 开发人员工具栏中使用),结果很明显。

正如您所见,在我的渲染循环中访问 Canvas 的宽度和高度花费了大量时间!

原始代码是

var renderingLoop = function () {


    for (var y = -canvas.height / 2; y < canvas.height / 2; y++) {
        for (var x = -canvas.width / 2; x < canvas.width / 2; x++) {

            ...

        }
    }
};

您可以通过两个先前填充了正确值的变量来删除 canvas.width 和 canvas.height 属性。

var renderingLoop = function () {

    var index = 0;
    for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
        for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
            ...
        }
    }
};

很简单,不是吗?有时可能很难意识到,但相信我,这是值得尝试的!

预计算

根据性能分析器,Math.atan2函数有点慢。事实上,此操作不是直接硬编码在 CPU 中,因此 JavaScript 运行时必须添加一些代码来计算结果。

总的来说,如果您可以预先计算一些耗时较长的代码,这总是一个好主意。在这里,在运行我的渲染循环之前,我计算了Math.atan2的结果。

// precompute arctangent
var atans = [];

var index = 0;
for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
    for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
        atans[index++] = Math.atan2(y, x) / Math.PI;
    }
}

然后可以在渲染循环中使用atans数组来显着提高性能。

避免使用 Math.round、Math.floor 和 parseInt

最后一个相关点是parseInt的使用。

当您使用 Canvas 时,您需要使用整数坐标(x 和 y)来引用像素。确实,您的所有计算都使用浮点数进行,最终您需要将其转换为整数。

JavaScript 提供了Math.roundMath.floor甚至parseInt来将数字转换为整数。但是这些函数会做一些额外的工作(例如,检查范围或检查值是否确实是数字。parseInt甚至首先将其参数转换为字符串!)。而在我的渲染循环中,我需要一种快速的方法来执行此转换。

回想起我旧的汇编代码,我用了一个小技巧:不要使用parseInt,您只需要将数字右移 0 位。运行时会将浮点值从浮点寄存器移到整数寄存器并使用硬件转换。用 0 值右移此值将使其保持不变,因此您可以将值转换回整数。

原始代码是

u = parseInt((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u);

而新的代码如下所示:

u = ((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u) >> 0;

当然,此解决方案要求您确定该值是正确的数字。

最终结果

应用所有优化后,您将获得以下报告:

您可以看到现在代码似乎已得到很好的优化,只有基本功能。

从隧道的原始渲染开始(没有任何优化)。

<在此处插入 tunnel.zip 包,例如此页面>

应用了所有这些优化之后。

<在此处插入 tunnel.zip 包,例如此页面>

我们可以通过以下图表总结每种优化的影响,该图表显示了我自己的计算机上测得的帧率。

进一步

牢记这些要点,您就可以为IE10 等现代浏览器或 Windows 8 的 metro 应用制作实时、快速、流畅的游戏!

© . All rights reserved.