游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

开发者实例论述如何向HTML5平台发展(1)

发布时间:2013-02-16 16:00:22 Tags:,,,,

作者:David Galeano, Duncan Tebbs

与其它平台一样,HTML5也遭遇了各种各样的批评,有些批评是正当的,但是也有许多是听信谣言或基于过时信息的荒谬之谈。不过不管怎样,该领域大多数成功的企业领导者还是非常支持HTML5,并且出现在turbulenz.com上的游戏也向我们证明了HTML5适用于高质量的游戏。所以我将在这篇文章中详细解释我们之所以支持,并愿意继续支持HTML5的原因。

HTML5能够快速将网页变成高质量游戏的最佳平台。瞄准这一网页标准的游戏不仅能够基于各种环境而运行,同时还能利用潜在硬件的强大能力以及现代设备的连通性。

Turbulenz投入了大量的研究和设计力量去创造一个面向网页的高性能游戏平台。基于主机的游戏技术开发背景,我们从游戏开发者的角度接近了HTML5以及相关技术(并始终牢记高保真度与性能)。

这一系列文章便是描写我们从主机转向网页所获得的重要经验:什么最有效,该何时使用,如何为解决持续的问题而优化策略和工作环境等。

有些基于Turbulenz的游戏能够利用turbulenz.com的游戏网站,即使用我们的HTML5平台和基础设施向在线玩家传达高质量的3D游戏。这家公司是在2009年初由一群来自Electronic Arts的主管和首席设计师所创建的,随后该团队不断扩展,又吸纳了来自世界各地主要娱乐公司的开发者和业务专家们。

turbulenzgroup(from gamasutra)

turbulenzgroup(from gamasutra)

在第一篇文章中,我们将讨论HTML5当前的发展以及与游戏相关的技术,并概述游戏开发者们所期待的开发环境和工作流程。

浏览器作为一个游戏平台

浏览器已经成为了一种全新的操作系统,而在呈现出各种问题的同时也带来了巨大的机遇。在过去,游戏总是孤立地存在着,在消耗资源的同时也从机器身上提取着完整的性能。运行于浏览器上的游戏代码将与其余的浏览器过程展开竞争,包括运行于其它标签或窗口的游戏(以及其它运行于该机器上的内容)。

在某种程度上,每个浏览器都必须被当成一个不同的平台:在所有浏览器中,有些界面较为常见,有些资源也是通用的,但从更高级的功能来看,开发者们必须在执行期间不断检查功能,并提供各种代码路径和变通方法。

包含浏览器,浏览器版本,OS,OS版本以及硬件的测试参数非常广泛。除此之外,尽管硬件资源不能被直接当成原生应用,但是开发者还必须处理图像驱动的漏洞以及藏在浏览器层面(与这些驱动程序连接在一起)的漏洞。

基于快节奏的浏览器更新,以及有些浏览器是进行自动更新,我们很难维持一个特定的支持版本列表进行测试。在Turbulenz,我们总是使用每个浏览器的最新版本以及一列最有名的浏览器版本。在写这篇文章时,我们面对的便是Explorer 8,9,10,Firefox 3.6以及最新版本,Safari 5.1以及最新版本(基于Mac OS X),Chrome的最新版本。我们使用了来自Nvidia,AMD以及Intel的视频卡去测试一系列硬件(从较低端的上网本到高端笔记本)。

作为开发平台,浏览器比其它平台拥有多个优势。总的来说,其迭代速度非常快(JavaScript在正常开发和更新时不需要经历编译阶段,只需直接加载和运行最新的代码版本)。网页开发者们一直热衷于使用一系列工具去排除故障并分析性能,而这也构成了非常有效的开发环境。

一般而言,我们会建议开发商保守地编译代码,在不同浏览器上进行测试,并不断监控新版本的状态。

基于JavaScript的游戏开发

只有一种通用编程语言能够用于浏览器中,那就是JavaScript。它还有一个官方名称:ECMAScript。我们并不是要在本文介绍这一语言,而是打算强调在基于C/C++背景使用JavaScript时所突出的一些元素或意想不到的性能影响。

特别是当我们将一个大项目从C/C++转移到JavaScript时,开发者们将会面对一定的挑战。JavaScript的语法与C语言类似,但其实它与功能语言(如Lisp)拥有更多共同点;它拥有闭包和一等函数。

我们发现闭包非常强大,能够更轻松且更清楚地创造异步程序。但是如果开发者不清楚自己的行为便会引起一些小问题。

例如,在循环中创造闭包并引用在执行循环时发生改变的变量便会引起一个常见的错误。因为变量是关于闭包执行时的价值(而不是在其创造时刻)。

JavaScript拥有一等函数意味着我们可以将函数当成参数进行传递,从其它参数过度给各种变量,并储存在词典库里。我们可以在运行时将字符串编译到函数中,不过出于安全原因,我们并不会建议开发者们这么做,因为字符串是源自未知的来源,并需要向代码的其它部分妥协。

语言具有面向目标的功能,但却不存在类别。JavaScript拥有构造函数和面向原型的继承。对象扮演着数据词典的角色,能够保存所有内容,并通过名称进行索引。我们还发现使用错误的名称去访问价值是个常出现的错误,而这通常是由代码的打字错误所引起的。

对象可以被指定为其它对象的原型。如果我们不能在一个对象中找到性能,那么运行时间将检查原型对象。我们可以将函数储存在对象中,而对象也可以分享原型,原型机制还可以让方法和代码基于相同的方式重新使用。除此之外,函数与对象一样也可以用于储存,并基于名称检索性能。

JavaScript并未拥有析构函数。当不存在任何参考时,JavaScript的运行时间将安排其销毁的时间点,并且JavaScript代码不会收到任何通知。我们发现在JavaScript代码中找到内存泄漏的重要性(也就是将参考传递到对象上时,对象拒绝收集任何无用的内容)。有些JavaScript分析工具所提供的对象数是基于它们所累积的快照,这一点虽然很有帮助,但是它们同样也要求对象必须源自非文字构造函数,并使用新的运算符(从而更好地进行区分)。有些分析工具同样也提供参考去追踪它们的堆积快照,这能帮助开发者鉴定哪些目标并不属于垃圾。

我们建议开发者们可以创建一个清晰且定义明确的对象所有权政策。这能让我们更轻松地指出哪些代码是用于维持特定对象的参考。

JavaScript并未拥有静态类型,所以变量可以拥有不同的类型,并且如果在同样的操作中使用不同类型变量便会出现自动转换。例如在字符串上添加数字将引起一连串的数字转变成字符串。最终导致我们很难找到问题所在,并对性能造成影响。

另外一个漏洞来源便是JavaScript逐位运算,即使用大端法次序并基于二进制补码格式将参数转变成32位体整数。举个例子来说吧,以下两种表述都是合理的:

(0×80 << 24) !== 0×80000000

(0×80 << 24) === -2147483648

如果使用的是无符号整数的话,这两种表述都将是错的。同时我们还需要使用三重远算符,因为如果操作对象具有不同的类型,它便不能执行类型的转换。相反的,双重运算符能够执行类型转换,并且能在代码中隐藏失误。

尽管JavaScript变量拥有布尔值,但是其它基本的类型也能够用于条件表达式中,以下名称都是用于表示错误值:零,未定义,空串,“0”,非数值。

JavaScript并未拥有区块范围,但是它拥有全局范围和函数范围。我们能在函数中随处创造并访问变量,就好似它们位于函数的最顶部。而这一点会让那些刚接触JavaScript的资深C++开发者感到困惑。

JavaScript也支持异常处理,但是其语言却不支持异常层次结构。随机运算符可以用于任何对象中;不管是对象,数字,字符串等等,它都是围绕着代码而进行的首次尝试。我们不可能基于一种特定类型只捕获一种对象。

语言从诞生起就不断进化着,并且有些功能并不是同时适用于所有浏览器。最近出现的一些功能都必须在使用前进行仔细检查。举个例子来说吧,对于我们来说,基于Object.defineProperty去定义对象属性的获得者和设置者的能力已经成为近来最有用的功能之一,但是如果不存在这种支持,我们便需要提供额外的代码路径。在某些情况下,测试现有的函数足以检查一个被支持的功能,就像Object.defineProperty。而在其它例子中,代码必须尝试着去使用捕捉区块中的功能并检查是否会出现异常情况。

我们发现JavaScript成为了最强大的一种语言,因为它拥有动态特性和功能特性,并且它也是大项目中的一种复杂语言。当许多开发者同时致力于一个代码基础上(拥有成千上万行代码)时,各种问题便很容易涌现出来。

在Turbulenz,我们尝试着利用开发过程(基于代码质量)去减少开发问题,包括:设置严格的代码标准,频繁地检查代码,设置自动单位测试等等。我们还经常使用静态分析工具去检查代码中是否出现一些常见的问题。直到现在,我们从未发现与C和C++具有同等检查标准的分析工具,不过也有一些工具带给我们不小的帮助,帮助我们识别了大量的漏洞,并且也在不断进行完善着,它们是:Closure Linter,JSHint以及JSLint。

当有新的软件工程师加入Turbulenz时,他们便会收到一本Douglas Crockford所编写的《JavaScript: The Good Parts》。我们认为这本书对于引导新人进入JavaScript非常有帮助,里面解释了一些常见的问题以及各种优秀的模式。

转换工具

有些开发者也许对那些能够将其它语言转换成JavaScript的翻译工具很感兴趣。这些工具让他们能够基于任何一种语言进行开发(如为了减少运行时的错误而使用静态输入),然后再将其转换成JavaScript。

最受欢迎的工具包括haXe,Dart以及Emscripten。TypeScript便是最近刚添加的一个项目,它能够延伸JavaScript,从而明确一些类型信息。这一工具非常有趣,因为它不仅能够提供有关错误检查与开发工具的优势,同时,TypeScript还能与JavaScript形成“反向兼容”。

我们总是会鼓励人们去使用这些工具而确保他们能够完全理解其中的含义。自动生成的JavaScript代码有可能会提供次优的性能或远超越手写JavaScript代码的规格。适合某一种环境的工具有可能并不适合于其它环境,所以我们便需要使用多种代码路径。调试问题和追踪早前的出错代码具有一定的难度,而在某种情况下,克服这些难度还需要我们去调整最初的源代码,并使用编译器而生成安全的JavaScript。并不存在支持特定浏览器的功能,并且这种功能也有可能限制我们所能获得的最终结果。

但是这些工具也拥有一些有趣的属性(游戏邦注:如将C++代码转换成JavaScript的能力,并且不需要进行垃圾收集),并且它们也始终在完善与发展着。我们必须将这些工具当成一种选择,并观察着它们的发展。

编辑器

静态语言的编辑器能够提供许多动态语言所不能提供的函数。

支持JavaScript的优秀编辑器总是能够提供:

语法高亮显示

关于下述内容的自动补全建议:

默认全局目标。

著名的外部库,如jQuery。

基于当前函数而定义的变量和函数。

基于当前文件而定义的全局变量和全局函数。

基于当前文件标准进行代码重构。

与JSLint或JSHint进行整合。

如自动补全定制对象,来自其它文件的全局变量等功能都仍缺少动态语言。有些编辑器开始基于本身的JavaScript引擎在当前项目中解析并执行所有JavaScript代码,以此更好地判断当前代码变量和属性的有效性。但是这些编辑器也总会生成一些错误或不完全的建议。使用特定的方法去编写代码也许能够帮助编辑器提供有效的建议,如在构造函数中优化所有属性。如此,编辑器便能够追踪是什么构造函数被用于创建当前的对象,并进一步检查其属性。

执行时间和性能

运行于虚拟机上的JavaScript带有严格的安全控制与有限的API,能够与外部世界进行有效的互动。最近的JavaScript引擎使用Just-In-Time(JIT)编译去生成机器代码,从而能比解释型字节代码更快速地执行。现代引擎正以极快的速度发展着,每次的改进都能让我们看到更加完善的性能以及内存使用率的降低。正如人们所想的,虽然这种性能不及精心编写的C/C++(基于浏览器,我们发现JavaScript的速度慢了4至10倍),但是随着每个新版本的问世,这种缺口正在逐渐被填满。

我们使用jsPerf跨越各种浏览器去评估小段代码的性能,并找到能够执行特定操作的最快方式。不幸的是,有时候在所有浏览器(以及所有相关版本)上并不存在最快的单一代码片段,所以我们不得不对此妥协,并去考虑市场占有率。

对于许多大型游戏来说,内存分配便是问题所在。适合512兆主机的对象需要在浏览器上占据十亿字节。比起C++,每个JavaScript对象需要投入更大的开销。

所使用的对象类型也会影响内存大小。JavaScript引擎所执行的数字通常都是32字节的带符号整数,或者64字节的浮点数,并总是占据着相同的内存(8字节)。

为了减少大数组的内存使用,我们建议使用类型数组。通过让持有3D向量的数组去使用Float32Array,我们为演示版本节省了20%的储存空间。

值得注意的是,类型数组总是比标准数组拥有更优秀的性能。大多数JIT编译器都知道如何直接处理类型数组所使用的基础内存,然后生成有效的代码,并在正确预测数据类型时有效执行这些代码。

就像之前所提到的,JavaScript使用垃圾收集的方法去处理那些没用的对象。垃圾收集是基于标记清除算法去寻找那些可被摧毁的对象。有些人也使用各个时代的理念去优化短期对象的分配与回收,并使用增量算法去分配标记与清除所需要的成本。

特定时间所存在的对象总数也将直接影响着垃圾收集成本。就拿早前的VM来说吧,当在收集过程中出现了上百万的活跃对象,它便会终止运行数秒。不过这种情况正在不断完善着,就拿当前来看吧,即使出现上百万个或额约对象,执行的延迟时间也只有数百毫秒,即使这仍预示着游戏中存在可感知的“跳过”。垃圾收集通常都是由连续的对象创造,或定期所触发的(游戏邦注:例如引擎每10秒可能会调用一次清除)。

并且我们也发现了让性能保持较低对象数量的重要性。我们的一些演示样本或例子并未在执行框架时创造一个单一对象。

我们发现了一些能够减少对象数量的策略。例如在不同函数或框架间反复使用动态数组便非常有效(如使用暂存器)。我们还考虑到基于交叉属性将对象数组转换成平面数组。在我们网站上的一款游戏中,这一方法便将对象数量减少了75%以上,并解决了垃圾收集过程中出现间歇的问题。

在某些情况下,编码信息和自定义字节码中的指令能为对象数权衡执行时间性能。举个例子来说,如果储存一个SVG路径,并且该路径支持着包含指令(关于特定形状的呈现)的单一字符串,同时还能破解这些指令,那么它所需要的内存和对象便少于提出字符串以及将指令当成一个层次的对象(尽管需要花费更多CPU时间)。

space ark(from gamasutra)

space ark(from gamasutra)

《太空方舟》便是一个典型的例子,我们便是使用这里所提到的一些方法去优化这款游戏。通过减少对象数量,我们不仅能够从根本上解决游戏过程中垃圾收集停歇的问题,同时还能保持早前Xbox Live Arcade版本的视觉效果,包括角色动画和粒子特效。

JavaScript代码的执行可能出现在单一线程中。最新的浏览器支持API去创造增加JavaScript的过程,就像Web Workers只能通过信息进行交流。过程与过程间并不存在直接的数据共享。

因为从本质上看来执行就是单线程的,如果JavaScript执行过长时间,浏览器便会提示用户停止失控的代码。如果用户同意的话,那么代码的执行将能立即终止,无需任何提示。即使用户并未停止代码,也会不断出现对话框去提醒他们长期运行的代码有多烦人。因此我们的建议是,如果想要进行长期代码执行,那就小幅度慢慢提升,使用浏览器所提供的计时器和间隔去管理未来可能出现的函数。

调试和性能分析

如今,所有浏览器都提供了一种嵌入式调试环境。调试器总是隐藏在开发工具菜单选择中。

调试功能通常包括:

穿透并检查HTML树。

记录并检查HTTP请求。

主机记录,读取-求值-打印循环代码片段的执行。

调试器支持:断点,堆叠追踪,变量观察。

浏览器所提供的剖析工具总是支持调用图像捕获(基于一种能够明显呈现出执行费用的设备)和堆积截图(关于对象计数器,对象大小以及它们之间的引用)。但是在不同浏览器中,这些功能及其执行质量也是不同的。

基于调试的代码,调试器可能运行于相同的过程以及相同的JavaScript环境中,但是这会降低它们的稳定性。我们发现当代码难度提高,或者调试器想要呈现过多变量信息时,它们最终便会遭遇崩溃。除此之外,我们也在执行代码的过程中遭遇定时器回调函数被调用的情况,而这将摧毁某些调用器并让开发者感到困惑。但是这些工具都非常有价值,并且将随着浏览器的发展而得到完善。

结论

这篇文章主要是对于HTML5游戏开发的综述,包括开发环境和工作流程的各种细节内容。而在之后的文章中我们也将进一步谈论有关HTML5的特殊功能及其相关标准。

本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Making the Move to HTML5, Part 1

by David Galeano, Duncan Tebbs

Like any platform, HTML5 receives its share of criticism, some valid and some based on rumor or outdated information. However, HTML5 is keenly supported by some of the most successful business leaders in the space, and the games available right now on turbulenz.com are proof that HTML5 is viable for high quality gaming today. We hope the below will help explain why we have backed, and continue to back, HTML5.

HTML5 and related standards are fast making the web a viable platform for high quality games. Games written to target such web standards can run in a variety of contexts while taking advantage of the power of the underlying hardware, as well as the connectivity enjoyed by modern devices.

Turbulenz has invested a significant amount of research and engineering manpower creating a high-performance games platform for the web. With a background in game technology development on consoles, we have approached HTML5 and related technologies from a game developer’s perspective with fidelity and performance in mind.

This series of articles describes some of our experience transitioning from consoles to the web: what works well, when to use it, optimization strategies and workarounds for ongoing problems.

Some of the games built using Turbulenz can be played on the turbulenz.com game network, which uses our HTML5 platform and infrastructure to instantly deliver high quality 3D games to gamers online. The company was founded in early 2009 by a group of directors and lead programmers from Electronic Arts, and the team has grown to include developers and business professionals from some of the world’s leading entertainment companies.

In this first article we talk about the current state of HTML5 and related technologies for games, and give an overview of the development environment and workflow that game developers can expect.

The Browser as a Game Platform

The browser has become the new OS, bringing with it a host of problems as well as opportunities. In the past, games traditionally ran in isolation, consuming as many resources as possible to extract the full performance from the machine. Game code running in the browser will be competing with the rest of the browser’s processes, including other games running in other tabs or windows (as well as whatever else may be running on the machine).

In some ways, each browser must be treated as a different platform: some interfaces are common across all browsers and some resources are always available, but for more advanced functionality, the developer must often check for features at runtime and provide multiple code paths and workarounds.

The test matrix covering Browser, Browser version, OS, OS version, and Hardware is large. Furthermore, although hardware resources cannot be accessed as directly as with native applications, developers still have to deal with graphics driver bugs as well as bugs in the browser layers that now interface with those drivers.

With the fast pace of browser updates and the fact that some browsers update themselves automatically, it can be difficult to maintain a specific list of supported versions to test against. At Turbulenz we always work with the latest version of each browser and also a list of well-known popular browser versions. At the time of writing, this includes Internet Explorer 8, 9 and 10, Firefox 3.6 and latest, Safari 5.1 and latest (on Mac OS X) and the latest version of Chrome. We test on a range of hardware from slow netbooks to high-end desktops using a selection of popular video cards from Nvidia, AMD, and Intel.

The relative market share of the browsers is always in motion and is usually not consistent across demographics. We recommend finding your target market and studying the browser stats for sites with a similar audience.

As a development platform, the browser has several advantages compared to other platforms. In general, iteration is extremely fast (JavaScript requires no compile step during normal development and refreshing the page immediately loads and runs the latest version of the code). Web developers have long enjoyed access to a range of tools for debugging and profiling, and these make up a very effective development environment. More details are given later in this article.

In general, when coding for the browser we recommend that developers code defensively, test with a range of browsers and regularly monitor the state of new versions.

JavaScript for Game Development

Only one general-purpose programming language can be used in the browser: JavaScript. It’s also known by its official name: ECMAScript. This is not intended to be a guide or introduction to the language, but we have highlighted some aspects that stood out to us or had unexpected performance implications when approaching JavaScript from a C/C++ background.

Particularly when moving a big project from C/C++ to JavaScript for the first time, developers will likely face some challenges. The JavaScript syntax resembles C, but the language has more in common with functional languages like Lisp; it has closures and first-class functions.

We found closures to be very powerful, making asynchronous programming much easier and clearer. However, subtle errors can occur if developers are not fully aware of their behavior.

For example, quite a common mistake is to create closures inside a loop, referencing variables that change during the execution of that loop. This can lead to confusing behavior since variables referenced by a closure contain the value at the point the closure is executed, not at the point of its creation.

JavaScript has first-class functions, meaning that functions can be passed as parameters, returned from other functions, assigned to variables and stored in dictionaries. Strings can be compiled at runtime into functions, although this is not recommended for security reasons as the string could come from an unknown source and could compromise other parts of the code.

The language has object-oriented features, but there are no classes. JavaScript has constructors and prototype-oriented inheritance. Objects behave like key-value dictionaries, able to optimally store and retrieve anything by name. We found that trying to access values by the wrong name was a common error, usually a typo in the code that stores or retrieves the data.

Objects can be assigned as prototypes of other objects. If a property is not found on an object then the runtime will check the prototype object, and so on. As functions can be stored on objects and objects can share prototypes, the prototype mechanism allows methods and code reuse in a familiar way. Functions also behave as objects, and can be used to store and retrieve properties by name.

JavaScript objects do not have destructors. The JavaScript runtime schedules their destruction once there are no remaining references to them, and JavaScript code does not receive any notification when that happens. We found it non-trivial to find memory leaks in JavaScript code (that is, hanging references to objects which are in turn never garbage collected). Some JavaScript profilers provide object counts on their heap snapshots, which does help, but they require that objects be created from non-literal constructors, using the new operator, in order to differentiate between them. Some profilers also provide reference tracking in their heap snapshots which helps to identify why a given object has not been garbage collected.

As a best practice, we recommend establishing a clear and well-defined policy of object ownership. This makes it easier to point to the code that is responsible for maintaining (and releasing) the reference to a given object.

JavaScript does not have static types, so variables are allowed to have different types over the life of the program and some automatic conversions will happen if different types are used in the same operation. For example, adding numbers to strings will result in a string concatenation of the number converted to a string. This can result in errors that are very hard to find and can have performance implications.

Another subtle point and possible source of bugs are the JavaScript bitwise operations, which convert parameters to signed 32-bit integers with big-endian order and in two’s complement format. For example, the following two statements are true:

(0×80 << 24) !== 0×80000000

(0×80 << 24) === -2147483648

Both would be false if unsigned integers were used. Note the use of the triple-equal operator, because it does not perform type conversion if the operands have different types. In contrast, the double-equal operator does perform type conversion and could hide errors in the code.

Although JavaScript variables can have Boolean values, every other basic type can be used on a conditional expression, with the following values being equivalent to false: null, undefined, the empty string, the number zero, the number NaN.

JavaScript does not have block scope; it has a global scope and function scope. Variables can be created anywhere in a function, and will be accessible everywhere, as if they were declared at the top of the function. This will confuse experienced C++ developers who are new to JavaScript.

Exceptions are supported in JavaScript, but the language does not support an exception hierarchy. The throw operator can be used on anything; an object, a number, a string, and it is caught by the first try / catch surrounding the code. There is no way to catch only objects of a specific type.

The language has evolved considerably from its origin and some features are not entirely standardized across browsers. Any recently added feature should be checked for before use. For example, the ability to define getters and setters for object properties with Object.defineProperty has, for us, become one of the most useful features added recently, however we need to provide extra code paths for when support does not exist. In some cases testing for the existence of a function is enough to check for a supported feature, as in the case of Object.defineProperty. In other cases, the code must try to actually use the feature inside a try / catch block and check whether an exception was raised.

We found JavaScript to be a powerful language because of its dynamic nature and functional features, but also a complicated language for big projects. With multiple developers working in parallel on a code base of hundreds of thousands of lines of code, mistakes will happen infuriatingly often without discipline and the right development tools.

At Turbulenz, we try to minimize development mistakes by employing a development process focused on code quality: strict coding standards, frequent code reviews and automatic unit testing. We also routinely use static analysis tools to check our code for common mistakes. So far we have not found analysis tools with the same level of inspection as those available for C and C++, but the following tools have been helpful in that they do catch a fairly large class of bugs, and they do improve over time: Closure Linter, JSHint, and JSLint.

When a new software engineer joins Turbulenz, they receive a copy of JavaScript: The Good Parts by Douglas Crockford. We consider this book a very good introduction to JavaScript, explaining common mistakes and good patterns.

Conversion Tools

Some developers may be interested in the translation tools available for converting other languages to JavaScript. They allow development in one language — such as one with static typing in order to minimize runtime errors — which is then converted to JavaScript.

Some of the more popular tools include haXe, Dart and Emscripten. The TypeScript project is a relatively recent addition that extends JavaScript to allow type information to be specified. This is particularly interesting since, as well as providing advantages in terms of error checking and development tools, TypeScript is “backwards compatible” with JavaScript.

We would encourage anyone considering using these tools to make sure they fully understand the implications. Automatically generated JavaScript code may provide suboptimal performance or a substantial size increase over hand-written JavaScript code. What works well in one environment may fail in a different one, requiring multiple code paths. Debugging problems and tracing back to the original offending code may prove difficult, and in some cases fixes may require tweaking the original source code to “trick” the compiler into generating safe JavaScript. Support for specific browser features may not exist, which will also limit what can be achieved.

However, these tools do have some interesting properties (such as the ability to turn C++ code into JavaScript that does essentially no garbage collection), and they are continually evolving and improving. It makes sense to be aware of them as an option, and to monitor their development.

Editors

Editors for static typed languages can provide a lot of functionality that is harder to provide for dynamic languages.

Good editors supporting JavaScript usually provide:

Syntax highlighting

Auto-completion suggestions for:

Default global objects.

Well-known external libraries like jQuery.

Variables and functions defined within the current function.

Global variables and global functions defined within the current file.

Refactoring at the current file level.

Integration with JSLint or JSHint.

Features such as auto-completion for custom objects, global variables from other files etc. are still lacking for dynamic languages. Some editors are starting to parse and execute all the JavaScript code in the current project in their own JavaScript engines to better judge the availability of variables and properties at the current point in the code. However these editors will frequently generate incorrect or incomplete suggestions. Writing your code in specific ways may help the editor with its suggestions, for example by initializing all the properties in the constructor. In this case the editor may be able to track which constructor was used to build the current object and inspect it for properties to suggest.

Runtime and Performance

JavaScript runs on a Virtual Machine with tight security controls and a limited set of APIs to interact with the external world. Recent JavaScript engines employ Just-In-Time (or JIT) compilation to generate machine code that executes much faster than the traditional interpreted bytecode of the past. Modern engines evolve at a fast pace, improving performance and reducing memory usage with every release. As one would expect, performance is not up to the level of well-written C / C++ (we found JavaScript to be 4 to 10 times slower depending on the browser) but the gap keeps closing with every new version.

We use jsPerf to evaluate performance of small snippets of code across browsers and find the fastest way to perform a given operation. Unfortunately there is sometimes no single code snippet that is fastest on all browsers (and all of their versions) and so we are forced to compromise, usually based on market share.

Memory allocation will become an issue for big games. What fits well in a console with 512 MB can take over a gigabyte in a browser. Every JavaScript object has a higher overhead compared to the C++ equivalent.

The type of objects used can also affect memory size. JavaScript engines implement numbers as either 32-bit signed integers or 64-bit floating points, and usually both take the same amount of memory (8 bytes).

In order to reduce the memory usage of big arrays of numbers we recommend the use of typed arrays where possible. We made a memory saving of 20 percent in one of our demos just by switching arrays holding 3D vectors to use Float32Array.

Note that typed arrays have generally better performance characteristics than standard Arrays. Most JIT compilers understand how to directly address the underlying memory used by typed arrays, and can generate extremely efficient code to access and operate on them when the data type can be correctly predicted.

As mentioned above, JavaScript uses garbage collection to dispose of objects that are no longer referenced. Garbage collectors employ mark-and-sweep algorithms to detect objects that can safely be destroyed. Some also use a concept of multiple generations to optimize the allocation and deallocation of short-lived objects, and incremental algorithms may be employed to distribute the cost of marking and sweeping. This is also an area of heavy active development.

The total number of objects alive at a given time has a direct effect on the cost of garbage collection. Older VMs will stop the world for seconds during collection if there are millions of active objects. This situation keeps improving and, nowadays, even millions of objects might only stall execution for hundreds of milliseconds, although this still manifests as a perceptible “skip” in a game. Garbage collection is usually triggered by either a lot of object creations in succession, or at fixed periods of time (for example the engine may invoke a full sweep every 10 seconds).

Not surprisingly, we have found it very important for performance to keep the number of objects we create as low as possible. Some of our demos or examples do not create a single object during execution of a frame.

There are several strategies we have found to be useful for reducing object count. Reusing dynamic arrays from function to function and from frame to frame (i.e. using a scratchpad) can be very effective. Also, consider converting Arrays of Objects to flat arrays with interleaved properties. In one of the games on our site, this alone reduced object count by over 75 percent and solved problems with garbage collection pauses.

In some cases, encoding information and commands in custom bytecode can be a way to trade off runtime performance for object count. For example, if storing an SVG path, maintaining a single string that contains instructions for a particular rendering shape and decoding these instructions on the fly will likely use a lot less memory and many fewer objects (although more CPU time) than unpacking the string and storing the instructions as a hierarchy of objects.

The Space Ark title is an example of where we worked very closely with the developer to optimize the game using some of the ideas given here. By drastically reducing the number of dynamically created objects we were able to essentially remove garbage collection pauses during gameplay on modern JavaScript runtimes, and still maintain the visual quality of the original Xbox Live Arcade version, including character animations and particle effects.

Execution of JavaScript code can be assumed to happen in a single thread. The latest browsers do support an API to create an equivalent to multiple JavaScript processes, known as Web Workers (described in a future article) that can only communicate via messages. No direct data sharing between processes is allowed.

Since execution is essentially single threaded, if JavaScript code executes for too long without yielding, the browser may prompt the user to stop the runaway code. If the user agrees, execution of the code will stop immediately and without warning. Even if the user does not stop the code, repetitive dialog boxes warning about long running code are likely to be annoying. Therefore we recommend that long-running work be done in small increments, using the timers and intervals provided by the browser to schedule functions to be executed in the future.

Debugging and Profiling

All browsers now provide a debugging environment embedded as part of the browser itself. The debugger tends to be hidden under a Development Tools menu option or similar.

Debugging features provided usually include:

Traversal and inspection of the HTML tree.

Recording and inspection of HTTP requests.

Console logging, a read-eval-print loop for executing snippets of code.

Debugger with support for: Breakpoints, Stack traces, Variable watching

The profilers provided by the browsers often support call-graph capture (based on a form of instrumentation which can add a noticeable overhead to the execution) and heap snapshots (with object counters, objects size and references between them). However, these features and the implementation quality can vary between the browsers.

Debuggers may run in the same process and the same JavaScript context as the code they try to debug, which reduces their stability. We found that all debuggers will eventually crash or fail either when code complexity becomes too high for them, or when they try to show too much information about watched variables. We also suffered from timer callbacks being invoked while stepping through code, which can break certain debuggers and can be very confusing for the developer. However, these tools are extremely valuable and are improving as the browsers are developed.

Conclusion

This article has been an overview of high-end game development for HTML5, including details of the development environment and workflow. In following articles we will talk more about particular features exposed by HTML5 and related standards that are of interest to games. As well as covering specific areas of game development such as Graphics and Audio, we will give tips and recommendations for how to extract the best quality and performance across a wide range of browsers and platforms.(source:gamasutra)


上一篇:

下一篇: