临渊羡鱼,不如退而结网—HTML5 2DCanvas手机游戏开发经验谈


现在什么技术最火?HTML5!现在什么技术最时尚!还是HTML5!也许有泡沫和炒作的成分,但是不得不承认,这个世界上本就没有所谓的公平。多年来兢兢业业带给我们无数快乐痛苦和就业机会的Flash,在代表着众多巨头利益,从蹒跚学步起就被内定为太子的HTML5面前,也许注定就是为他人做嫁衣的命。


当你在网上看到铺天盖地的HTML5新闻,各种各样炫目的DEMO,同时脑袋里充斥着各种新鲜的名词,什么矢量动画、视频标签、离线存储、地理信息、新型表单、语意标签等等等等的时候。你是否会有一些迷茫?到底什么是HTML5?应该从何处入手?


我们的核心业务之一便是手机社区游戏,但是以精武堂为首的游戏仍然是以WAP网页的形式而存在。在iphone和android智能手机越来越普及的今天,这种模式必然是无法可持续发展的。于是危机感让我们把目光的一部分转向了HTML5技术,面对着那些令人眼花缭乱的技术新名词,我们抱着与其临渊羡鱼,不如退而结网的想法,选择了其中最核心的Canvas技术将社区游戏阳光牧场搬到了HTML5移动平台。


在开发的过程中,我们遇到了各种各样的问题,其中比较有代表性的问题是:交互兼容性、性能以及机型适配的问题。而性能问题则是最核心的问题。下面将分别进行介绍。


1.交互兼容性


很多时候,“跨平台”三个字已经成了鸡肋和枷锁,不知多少优秀的产品死在这上面。不过作为预研项目,很有必要在这上面做尝试。再说了,跨平台不是HTML5最大的噱头么?


这里所说的跨平台,不是传统的linux和Windows,也不是说兼容android和ios,更不是指IE和Firefox,而是指同时跨PC、平板以及手机设备。这种范围的跨平台就决定了这不仅仅是一个简单的技术问题,而同时是一个交互问题。


下表是PC和触屏设备的常用交互方式对比。


   
  

PC平台(键盘、鼠标)

  
  

平板设备(触屏)

  
  外接键盘输入
  

×

  
  

  
  单击
  

  
  

  
  双击
  

  
  

×

  
  拖动
  

  
  

  
  多点触控
  

×

  
  

  

结论非常明显,我们的游戏只能选择单击和拖动两种交互方式。在进行游戏交互设计的时候就必须考虑到这个残酷的现实。不过幸运的是,对于普通的社区游戏或者休闲游戏来说,这两种方式就足以完成全部的交互了。


1.1 一些技术细节


PC上的就不说了,这里只说Android和IOS。HTML5对于触屏设备的单点触控提供了touchstart,touchend,touchmove和touchcancel四种事件。而通过测试表明,一旦监听了这四种事件中的任意一种,很多设备将不会触发click事件。这样的话,要想实现拖拽和点击两种交互操作,就只能通过上面这一组touch事件来组成。我们使用的是以下的方案:


  

事件顺序

  
  

操作

  
  

touchstart->touchend

  
  

点击

  
  

touchstart->touchmove(多个)->touchend

  
  

拖拽

  
  

touchstart->touchmove(多个)->touchcancel

  
  

拖拽

  
  

touchmove(多个)->touchcancel

  
  

划过

  
  

touchmove(多个)->touchend

  
  

划过

  

也就是说,如果如果刚触发完touchstart事件马上就触发touchend,说明手指只是轻轻点了一下屏幕,也就是所谓的点击操作。这样即使不监听click事件也能实现点击的侦听。不过这里有一个实际的情况,很多山寨的Android设备屏幕很不灵敏,比如我们公司拿到的电信酷派D530手机,需要使劲按下才能有所感知。这种情况下一定会触发touchmove事件。所以针对Android设备的点击操作可以适当放宽,比如touchstart和touchend之间可以允许有少量几个touchmove,并且touchmove的距离不能超过多少个像素等等。


2.性能


对于手机上的HTML5游戏性能,我只能用坑爹两个字来形容。从硬件上来讲,由于电压和芯片架构的不同,同样主频的手机性能和PC上进行相比差距甚远。再加上HTML5本身是一种解释型的脚本语言,其运行效率也远远低于本地应用。


废话不多说,没图没真相。



     这是不同分辨率的图在不同设备上渲染50次所需要的事件,y轴的单位为毫秒,最下面两个几乎耗时为0的是PC上的chrome和safari浏览器,在上面最大耗时为800左右的两根线条代表Ipad和Iphone,再往上就是XOOM平板以及其他Android手机了。


       由此可见,移动设备尤其是Android设备的渲染性能非常不乐观。最早一个版本的游戏DEMO,在PC上可以跑到60多帧,IPHONE4上为16帧,而在HTC G9上只有2-3帧,完全无法接受。鉴于HTML的Canvas只为我们提供了非常简单的接口。所以我们决定对整个游戏进行优化。


2.1 脏矩形


      这是一个非常古老的技术,从任天堂红白机时代就开始使用了。其原理是每一帧只重绘屏幕上有变化的区域。这样可以大大减少渲染管道中的数据传输量,从而达到提高性能的目标。随着硬件技术的发展,2D画面早已不是性能瓶颈所在,所以虽然MFC以及FLASH等开发框架中已经自动集成了脏矩形机制,但是这种思想已经渐渐从一般开发者的脑中消失。不过鉴于移动设备上html5游戏倒霉的性能,不得不手动重新发明了一次轮子。假如说有这样两层图片组合成一个游戏画面:


+

=


而在下一帧我希望将游戏画面变成这样:



按照脏矩形的思想,我们只需要重绘中间小人的区域就行了。其具体做法是先清空小人所属的矩形区域的全部图像,然后按照渲染对象从底层到最高层的顺序依次重绘那个区域。之所以连底层的都要重绘是考虑到可能有透明的部分存在。如果可以保证最上面变化的一层能完全覆盖整个变化的矩形区域,则可以不对下面的层进行清空和重绘,直接重绘最上面一层覆盖即可。其步骤如下所示:



       这样,就完成了整个重绘区域的绘制。像休闲游戏这样的非动作类游戏,比如围棋,象棋,斗地主等,由于没有滚屏,所以画面绝大部分都是静止的,采用了脏矩形技术后可以大大提高性能。


2.2 龟速的六参数仿射变换模型


       在平面图形学中,仿射变换(Affine Transform)模型是一种最常用的变换模型,由于其中有6个参数,所以又叫做六参数模型。


       在2D游戏开发中,画面上所有的对象一般都呈现一个树形的数据结构,比如说有背景这个根对象,根对象下面又有房屋,人物等子对象。而人物对象下面也许还有服装和武器等更多的子对象。每个子对象只记录自己相对于自己的父对象的位置关系。一般来说有X轴平移坐标,Y轴平移坐标,X轴缩放比例,Y轴缩放比例,旋转角度等。那么假设一个子对象内部的坐标(x1, y1),相对于其父对象内部的坐标系就可以表示为(x2, y2),两者的关系为:

   

       x2 = A*x1 + B*Y1+ C


       y2 = D*x1 + E*Y1+ F

      其中,A,B,C,D,E,F六个参数就是父子对象之间的仿射变换参数。用图来表示就是:


       其实我放出这个图的目的并不是为了说清楚六参数要怎么计算,因为我也看不懂。我只是想表示这个模型的计算量有多大。如果这样说还不够清楚,那么就用事实来说明。参数里的A,B,C,D,E,F六个参数是用X轴平移坐标,Y轴平移坐标,X轴缩放比例,Y轴缩放比例,旋转角度这5个物理意义参数所计算出来的,其中光是A的计算就包含了全部5个物理意义参数的加减乘数和三角函数。


       在实际应用中,我们整个游戏都运行在一个HTML的Canvas标签之内,我们只知道这个标签接受到了交互事件以及交互事件的坐标,具体要知道是我们游戏内哪个精灵正处在这次的交互点上,就必须进行相对的坐标计算。一个从根目录算下去第五层的子对象就必须计算5次仿射变换矩阵,每次计算都要计算A,B,C,D,E,F六个参数,其计算量可以说对于手持设备已经达到了无法容忍的地步。事实上,通过一些热点profile工具,我们已经发现,在采用了脏矩形技术之后,整个游戏的性能瓶颈就在于这样的数值计算上。


       于是出于性能上面上的考虑,我们果断放弃了六参数仿射变换模型,而采用了简化的四参数模型。


2.3 采用简化四参数模型


       所谓简化四参数变换模型,其实就是舍弃了旋转功能,这样的话物理意义上的参数就只剩下X轴平移坐标,Y轴平移坐标,X轴缩放比例,Y轴缩放比例四个,表面上只减少了一个参数,实际计算上却大大减小了。现在的变换关系可以表示为:


       x2 = X轴缩放比例*x1 + X轴平移


       y2 = Y轴缩放比例*y1 + Y轴平移


       其最大的优势就是不用再计算变换参数,而是直接采用物理意义参数即可。


       其实放弃旋转功能,采用四参数模型除了性价比意外,还有一个不得已的原因。在Canvas中,要实现旋转功能必须通过rotation参数指定旋转幅度,而一旦使用了这个东西,浏览器底层就会通过transform函数来调整内部的仿射变换参数矩阵。悲哀的是,Android 2.1的某个版本自带的浏览器这个参数计算实现是错误的,是在侮辱我们的数学知识。


       比如一张图片经过六参数变换矩阵扭曲变形后应该是如下图所示:


       而在一部分Android 2.1设备上,却是如下景观:


       因此,就算是不为了性能,只为照顾广大的Android平台,我们也必须舍弃Canvas自带的transform六参数变换,采用自己实现的简化四参数模型。


2.4 各种缓存


       用空间换时间,永远是最牛逼,最屌爆,最简单的性能优化方式。简单说来,有以下几点:


       1. 全局仿射参数的缓存


       上文说道,一个嵌套了多层的子对象每次需要递归计算变换参数到根对象才能计算其全局坐标。而事实上,当对象静止不动的时候,这玩意儿只要计算一次就好了。因此,以下3个参数当对象或者其上层对象没有移动缩放的时候,是可以缓存下来的:


       四参数模型的四个参数,坐上点的全局坐标,右下点的全局坐标


       2. 用户操作事件的缓存


       像拖拽这种操作,很可能在2帧之间会触发好几次touchmove事件,难道就真的计算那么多次对象的移动么?中间几次的计算不是喂狗了么?又看不到。所以接收到用户操作事件先不要忙着响应,直接保存下来,然后到下一帧再提出来进行排重和触发。


       3. 对象树的图片化功能


       在游戏中,画面上很可能层叠了几十上百个精灵,当画面整体移动的时候,必须对整个屏幕区域进行全面重绘。暂且不说浪费的那么多需要重复绘制的重叠面积。单说那么多精灵的变换参数需要每一帧计算就够蛋疼了。所以必须提供一种功能,在某种时刻,可以将某个对象以及其所有子对象渲染到一个临时的Canvas上。然后用这个新的Canvas作为一副图片来替换之前的那么多对象。这样的话一是只需要计算这一个对象的参数变换,二是也省去了精灵之间的重叠区域的重绘面积。


       4. 对文字和矢量图进行图片化


       相对于图片渲染,矢量图和文字的性能更为底下。所以必须要将其采用第三点的方式进行图片话。另一个原因是因为采用四参数模型而舍弃了任何可能调用transform函数的操作之后,已经没有任何方式可以对文字和矢量图进行缩放。所以必须将其渲染为图片使用。


3. 一些总结,一些吐槽


3.1 和Flash的对比


       现在有很多人在叫嚣Flash已经没有前途,未来是HTML5的天下。甚至本地应用都应该被HTML5替代。我认为这要不就是死玩技术的极客,要不就是跟风的喷子。我们做产品,首先要从用户的角度去思考,HTML5可以为用户带来什么,是客户端软件和FLASH所无法提供的。


       1. 不需安装


       2. 对于不能上APPSTORE的应用,非越狱IOS用户也可享受


       3. 升级不再需要用户更新客户端


       4. 对于简单非游戏类应用来说,更加省电


       对于我们开发来说,最大的优势其实就只有一点:跨平台。从此不用再针对各个平台分别进行开发。

 


       但是同时HTML5也有很多先天的劣势:

       1. 没有高效率的可视化开发环境


       2. 比起Flash,没有现成的分模块加载机制,不利于设计大型的网页游戏


       3. 开发Canvas游戏提供的接口太过简单,几乎等于从种橡胶树开始造轮子。


       4. 对于游戏开发来说,性能还是一个瓶颈所在。

 

3.2 HTML5适合的游戏类型


       非动作类的游戏,相对交互比较简单的,比如斗地主和棋类游戏,对对碰,找茬等。总之QQ手机游戏大厅里的游戏都比较适合,而且配合离线存储可以免去升级给用户带来的苦恼。


3.3 一点广告


       目前ipad版HTML5欢乐斗地主已经上线了,请用ipad自带的safari浏览器登陆h5.qq.com进行体验。

这不是最好的时代,也不是最坏的时代。


这只是黎明前的黑暗,未来一片光明。