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

来源:互联网 发布:linux java heap size 编辑:程序博客网 时间:2024/05/30 07:12

作者:David Galeano, Duncan Tebbs

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

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

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

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

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


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)

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

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

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

调试和性能分析

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

调试功能通常包括:

穿透并检查HTML树。

记录并检查HTTP请求。

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

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

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

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

结论

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

来源:游戏邦



原创粉丝点击