优化您的 Firefox OS JavaScript 游戏

在拥有 16GB 内存的四核处理器上进行开发时,很容易忘记考虑它在移动设备上的性能。本文将详细介绍将游戏移植到 Firefox OS 或类似硬件目标的一些最佳实践和注意事项。

充分利用 256 Mb RAM/800 Mhz CPU

在开发游戏时,有很多方面需要考虑。当您的目标是每秒绘制 60 次时,垃圾收集和低效的绘制调用会开始阻碍您。让我们从基础开始……

不要过度优化

这在关于游戏优化的文章中听起来可能违反直觉,但优化是最后一步;对完整、可工作的代码进行优化。虽然记住这些提示和技巧永远不会错,但您在完成游戏并在设备上玩过之后才知道是否需要它们。

优化绘制

HTML5 2D 画布 上绘制是大多数 JavaScript 游戏中最大的瓶颈,因为所有其他更新通常只是代数运算,不会触及 DOM。画布操作是硬件加速的,这可以为您提供一些额外的喘息空间。

使用全像素渲染

当您在画布上渲染对象时,使用非整数的值会导致子像素渲染。

ctx.drawImage(myImage, 0.3, 0.5)

这会导致浏览器进行额外的计算来创建抗锯齿效果。为了避免这种情况,请确保在调用 drawImage 时使用 Math.floor 对所有坐标进行四舍五入,或者如您将在本文中进一步阅读,使用 按位 运算符。

jsPerf – drawImage 全像素.

在离屏画布中缓存绘制

如果您发现自己每一帧都有复杂的绘制操作,请考虑创建一个离屏画布,在离屏画布上绘制一次(或在每次更改时绘制),然后在每一帧上绘制离屏画布。

myEntity.offscreenCanvas = document.createElement(“canvas”);
myEntity.offscreenCanvas.width = myEntity.width;
myEntity.offscreenCanvas.height = myEntity.height;
myEntity.offscreenContext = myEntity.offscreenCanvas.getContext(“2d”);

myEntity.render(myEntity.offscreenContext);

在画布标签上使用 moz-opaque(仅限 Firefox)

如果您的游戏使用画布并且不需要透明,请在画布标签上设置 moz-opaque 属性。此信息可以在内部使用来优化渲染。


Bug 430906 – 在画布上添加 moz-opaque 属性 中描述了更多信息。

使用 CSS3 变换缩放画布

CSS3 变换通过使用 GPU 更快。最佳情况是不缩放画布,或者使用更小的画布并向上缩放,而不是更大的画布并向下缩放。对于 Firefox OS,目标尺寸为 480 x 320 像素。

var scaleX = canvas.width / window.innerWidth;
var scaleY = canvas.height / window.innerHeight;

var scaleToFit = Math.min(scaleX, scaleY);
var scaleToCover = Math.max(scaleX, scaleY);

stage.style.transformOrigin = "0 0"; //scale from top left
stage.style.transform = "scale(" + scaleToFit + ")";

这个 jsFiddle 中查看其工作原理。

缩放像素艺术的最近邻渲染

从上一条开始,如果您的游戏主题是像素艺术,您应该在缩放画布时使用以下技术之一。默认的调整大小算法会产生模糊效果,破坏了美丽的像素。

canvas {
  image-rendering: crisp-edges;
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-optimize-contrast;
  -ms-interpolation-mode: nearest-neighbor;
}

或者

var context = canvas.getContext(‘2d’);
context.webkitImageSmoothingEnabled = false;
context.mozImageSmoothingEnabled = false;
context.imageSmoothingEnabled = false;

有关更多文档,请参阅 MDN 的 image-rendering

用于大型背景图像的 CSS

与大多数游戏一样,如果您有一个静态背景图像,请使用带有 CSS 背景属性的普通 DIV 元素,并将它放置在画布下方。这将避免在每次滴答时将大型图像绘制到画布上。

用于图层的多个画布

与上一条类似,您可能会发现有些元素经常改变和移动,而其他元素(例如 UI)则从未改变。在这种情况下,一种优化方法是使用多个画布元素创建图层。

例如,您可以创建一个位于所有内容顶部的 UI 图层,并且只在用户输入时进行绘制。您可以创建一个游戏图层,其中包含经常更新的实体,以及一个用于很少更新的实体的背景图层。


不要在 drawImage 中缩放图像

在加载时将图像的各种尺寸缓存到离屏画布中,而不是在 drawImage 中不断缩放它们。

jsPerf – Canvas drawImage 缩放性能.

小心使用重量级的物理库

如果可能,自己编写物理引擎,因为像 Box2D 这样的库在低端 Firefox OS 设备上表现不佳。

asm.js 支持登陆 Firefox OS 时,Emscripten 编译的库可以利用接近本机的性能。在 Box2d Revisited 中阅读更多内容。

使用 WebGL 而不是 Context 2D

说起来容易做起来难,但将所有繁重的图形处理工作交给 GPU 将释放 CPU 的能力,使其发挥更大的作用。即使 WebGL 是 3D 的,您也可以使用它来绘制 2D 表面。有一些库旨在抽象绘制上下文。

最小化垃圾收集

JavaScript 在内存管理方面可以宠坏我们。我们通常不需要担心内存泄漏或保守地分配内存。但如果我们分配了太多内存,并且垃圾收集发生在帧中间,这会占用宝贵的时间,并导致 FPS 明显下降。

池化常见对象和类

为了最大程度地减少在垃圾收集期间被清理的对象数量,请使用一个预先初始化的对象池,并重用它们,而不是一直创建新对象。

通用对象池的代码示例


避免内部方法创建垃圾

有各种 JavaScript 方法创建新对象,而不是修改现有对象。这包括:Array.sliceArray.spliceFunction.bind

阅读有关 JavaScript 垃圾收集的更多信息

避免频繁调用 localStorage

LocalStorage 使用文件 I/O 并阻塞主线程来检索和保存数据。使用内存中的对象来缓存 localStorage 的值,甚至可以将写入操作保存到玩家不处于游戏中间时。

抽象存储对象的代码示例


使用 IndexedDB 的异步 localStorage API

IndexedDB 是一种用于在客户端存储数据的非阻塞 API,但对于小而简单的数据来说可能过大了。Gaia 的库可以使 localStorage API 异步,使用 IndexedDB 可在 Github 上获得:async_storage.js

其他微优化

有时,当您用尽了所有选项,它仍然无法更快时,您可以尝试以下一些微优化。但是请注意,这些优化只有在重度使用时才会产生效果,每毫秒都很重要。在您的热游戏循环中寻找它们。

使用 x | 0 而不是 Math.floor
使用 .length = 0 清空数组,避免创建新数组
牺牲一些 CPU 时间来避免创建垃圾。
使用 if .. else 而不是 switch
jsPerf – switch vs if-else
Date.now() 而不是 (+ new Date)
jsPerf – Date.now vs new Date().getTime() vs +new Date
或 performance.now() 用于毫秒级以下的解决方案
对于浮点数或整数(例如向量和矩阵)使用 TypedArrays
gl-matrix – 用于高性能 WebGL 应用程序的 Javascript 矩阵和向量库

结论

为移动设备和性能不高的硬件构建是一个很好的创造性练习,我们希望您能确保您的游戏在所有平台上都能良好运行!

关于 Louis Stowasser

我是 Mozilla 的合作伙伴工程师,Gamedev Weekly 的维护者,以及位于澳大利亚布里斯班的 CraftyJS 游戏引擎的创建者。

更多 Louis Stowasser 的文章……

关于 Harald Kirschner (digitarald)

Harald "digitarald" Kirschner 是 Firefox 开发人员体验和工具的产品经理——致力于赋予创作者权力,让他们编写代码、设计和维护一个对所有人开放且可访问的网络。在他在 Mozilla 的 8 年时间里,他在性能、Web API、移动设备、可安装 Web 应用程序、数据可视化和开发人员推广项目中不断提升自己的技能。

更多 Harald Kirschner (digitarald) 的文章……

关于 Robert Nyman [荣誉编辑]

技术布道师和 Mozilla Hacks 编辑。进行 HTML5、JavaScript 和开放网络方面的演讲和博客。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年起,一直在瑞典和纽约市从事 Web 前端开发工作。他还定期在 http://robertnyman.com 上发表博客,喜欢旅行和结识新朋友。

更多 Robert Nyman [荣誉编辑] 的文章…


12 条评论

  1. Maurizio Lupo

    好文章!
    只是一个小问题
    使用元视窗 (https://mdn.org.cn/en-US/docs/Mozilla/Mobile/Viewport_meta_tag) 来调整整个游戏屏幕的大小怎么样?

    2013 年 5 月 30 日 上午 1:39

    1. Harald Kirschner

      对于画布游戏,我建议将视窗锁定到设备宽度并禁用缩放。前面提到的缩放机制主要是为了让游戏画布适应多个屏幕尺寸,在桌面端也效果很好。

      2013 年 5 月 30 日 上午 9:06

  2. Rikard Herlitz

    很棒的文章!但要注意,用 Math.floor 替换负值时会有所不同。

    2013 年 5 月 30 日 上午 2:27

    1. Harald Kirschner

      是的,这是一个很好的观点。有很多位运算,我希望开发者能够选择对自己有效的运算,并相应地调整输入。

      2013 年 5 月 30 日 上午 8:35

  3. Ashley

    感谢您链接到我们的“如何编写低垃圾实时 JavaScript”!我们最近还写了一篇关于为移动设备最小化内存使用的博客,因为一个常见的错误是使用超过内存容量的图像:https://www.scirra.com/blog/112/remember-not-to-waste-your-memory

    2013 年 5 月 30 日 上午 5:02

    1. Harald Kirschner

      现在您发布了,我记得把它放进了我的阅读队列。这两篇文章都提供了很好的建议。WebGL 在内存使用方面很有帮助,因为它可以使用压缩纹理,这些纹理可以在 GPU 中使用而无需解压缩。

      2013 年 5 月 30 日 上午 8:28

      1. André Fiedler

        供参考:https://mdn.org.cn/en-US/docs/Web/WebGL/Using_Extensions#WEBGL_compressed_texture_s3tc

        2013 年 5 月 31 日 上午 0:23

  4. André Fiedler

    “使用 if .. else 而不是 switch”

    但是 Firefox 21 到 23 中,“switch” 比“if … else” 快?!

    -> http://jsperf.com/switch-if-else

    2013 年 5 月 30 日 上午 7:35

    1. Harald Kirschner

      有趣,看来我们已经优化掉了。我猜 JIT 现在以非常类似的方式转换这些结构;看看它们在 FF 18(当 IonMonkey 登陆时)有多接近。

      2013 年 5 月 31 日 上午 11:20

  5. Jim McGinley

    很棒的文章。在 PC 上,离屏画布似乎没有帮助。我还以为,将内容绘制到一个小型的离屏画布上,然后再将其缩放到屏幕上,可以提高性能。结果不是这样!
    以下是我的测试(搜索“离屏”):
    http://www.bigpants.ca/html5test/test.html

    移动端可能会有所不同。

    2013 年 6 月 5 日 上午 10:35

  6. Ed

    是的,太棒了,感谢您!

    2013 年 6 月 21 日 上午 0:36

  7. Rob Walch

    我不会使用 transform 来缩放任何需要在每帧更新的元素。每次更新画布时,都需要将新的纹理加载到 GPU 上。只需使用 width 和 height 进行缩放,并让浏览器或操作系统处理如何以最快的速度绘制画布。FireFox-OS 可能不是这样,但我还没有发现它在大多数 WebKit 浏览器中能够改善渲染。

    一个对我在 Chrome 中很有帮助的技巧是,将不在每帧绘制的图像复制到画布,这样它们就可以保留在内存中。如果图像对象没有被绘制,则可能会从内存中删除未压缩的数据,从而在再次绘制它们时触发额外的 png/jpg 解压缩调用。

    除此之外,这些建议都非常棒。谢谢!

    2013 年 6 月 22 日 下午 3:08

本文的评论已关闭。