QQ空间萌宠之舞:HTML5骨骼动画实践

企业动态
QCon是由InfoQ主办的全球顶级技术盛会,每年在伦敦、北京、东京、纽约、圣保罗、上海、旧金山召开。自 2007年 3月份首次举办以来,已经有超万名高级技术人员参加过QCon大会。QCon内容源于实践并面向社区,演讲嘉宾依据热点话题,面向 5年以上工作经验的技术团队负责人、架构师、工程总监、高级开发人员分享技术创新和最佳实践。

 4月16日QCon前端工程实践专场会议上,腾讯QQ空间营收方向负责人、web前端工程师李振文以”QQ空间萌宠之舞——HTML5骨骼动画实践”为主题,用去年上线的QQ空间萌宠作为实践案例,为大家分享了骨骼动画的实践技术点、骨骼动画实践中遇到的问题以及最终的解决方案,***是根据总结的经验得出得性能优化方案。

李振文:我是来自QQ空间的李振文,很高兴在QCon上跟大家交流,QQ空间去年上线了一个宠物,我们使用了骨骼动画实现这个游戏。从零基础最终到这个游戏上线,我们开发实践过程当中遇到很多的问题。开发实践中遇到的问题和一些思路想和大家交流一下。

这是我今天介绍的一个大纲,首先给大家介绍一下骨骼动画的基础知识点。然后是项目中几个工程技术实践,以及遇到的问题和解决方案,***讲性能优化上面的几个实践点。

先介绍一下骨骼动画的基础知识点,我拿帧动画和骨骼动画的素材做了一个对比。屏幕上帧动画实现不停的切换每一张图片实现动画播放的。这样如果我们要去实现多套动作的话,就需要准备很多套图,灵活性很差,素材体积也大。但骨骼动画是将素材拆解成一个个零件,图片上人拆解成头、身子、盾、矛,然后用这一套素材拼成不同的动作,跑步、跳跃等都可以实现。骨骼动画设计上的做法跟我们代码模块化游戏相似。

这里列出了骨骼动画的特点。

***,资源体积更小,骨骼动画保存骨骼相关的动画数据,不需要把每一帧图片都保存下来,所以需要的资源很小,小的图片就可以实现。

第二,多角色可以共用同一套数据,我们实现一套骨骼之后,只需要把骨骼上面图片素材更换或者修改就可以实现不同的角色。

第三,动作可以自由组合,比如一个角色它可以这样摇头,摇头同时把手抬起来,把脚起来,可以自由组合。

第四,骨骼动画有网格的功能,可以实现蒙皮、自由变换,动画更加逼真。

第五,骨骼动画对处理器要求性能更高,骨骼动画运动的时候需要大量的计算,因此性能要求更高,这也是我们实践当中遇到的***挑战。

这是一个组装好的骨骼动画,图片中将骨骼显示出来的,每一条灰色线就是一根骨骼,骨骼上面的小黑点是关节,每个都可以旋转,骨骼动画运动只需要动一个骨骼,与之相连的骨骼就可以一起运动。这张图蓝色线条的部分,是手的上臂骨骼,移动这个上臂骨骼,子骨骼,前臂和手掌也跟着一起运动。这样就非常美妙了,我们实现耸肩只需要关注肩膀的运动,不需要关注手臂和手掌的运动。

 

 

这是我们整理出来的骨骼动画的组成结构,这个组成结构对应的动画编辑器是SPINE。包括有骨骼、插槽、附件和动画。骨骼是核心,是竖状结构,插槽附着到骨骼上面,插槽上面可以放很多附件,附件包括图片和网格,图片是我们肉眼可以看到的,图片素材头、脚、身子、腿这些。网格可以用来实现自由变形和蒙皮效果,让动画更加逼真。动画控制每一帧骨骼位移和旋转位置。

 

 

这是一个骨架的树状结构,这个骨架下面有躯干、左腿、右腿和盆骨。而躯干下面又有左臂、右臂和脖子。可以看到骨骼上面是附着有图片的,这些图片其实就是附着在骨骼的插槽上面,红色圆圈部分就是插槽。插槽是SPINE编辑器的一个概念,为了让骨骼不影响绘制顺序而创造的,例如,如果我的躯干部分有衣服和肚子,髋骨有裤子,绘制顺序应该是肚子在最下面,裤子在中间,衣服在最上面,如果没有插槽这个概念,那么就要建立三个骨骼,躯干要拆成两根骨骼,这样不合理,但是有了插槽就只需要两根骨骼,可以在躯干这一根骨骼上面建立两个插槽分别放衣服和肚子。

 

 

介绍完骨骼我们介绍一下网格,图片中蓝色的线条就是网格,一条一条的线非常形象。刚才讲网格实现蒙皮和自由变形。为什么可以实现自由变形?因为有顶点、边缘、三角区域三个概念。顶点就是蓝色的点。三角区域就是三个点组成的三角区域。边缘就是整个大框架的边缘。有了这三个概念,只要我们移动***可以将三角区域纹理进行变形,图片就变形了。***张图我们拖动蓝色的顶点,就可以拉长鼻子。蒙皮效果就是能够让骨骼运动影响网格的方法,进而影响图片素材的运动,运动效果显得更逼真。我们来看一个演示效果:


 

左边动画没有使用蒙皮,右边动画使用了蒙皮,左边耳朵和长矛是僵硬的,右边耳朵和长茅抖动显得更逼真,没有蒙皮效果的骨骼动画设计是不完整的。

做骨骼动画,***步选择对应的编辑工具,这影响后面动画实现和运行库的选择。现在市面上主要有两款,***个是SPINE,国外付费软件,运行库和功能都很多,而且普及程度高,大部分设计公司都用的这个,因为考虑后续设计资源的缘故,我们选择了SPINE。另一款是龙骨,我们国产软件,免费,运行库和功能相对较少,但是作者在不断完善中,功能也在向spine靠齐,而且关键的是可以直接与作者沟通。这里大家在学习的时候建议可以先用龙骨上手。后续再根据游戏的需求来选择用哪个软件。

SPINE支持的运行库非常多,官网上列了很多,我们这里挑了几个来做对比,主要从性能、是否支持webgl、文件大小、文档还有活跃度方面来做选择。因为几个底层库实现都相同,性能上没什么太大的差别;cocos2d的骨骼动画组件由于没人维护,直接放弃了;最终我们结合项目特色,选择了PIXI这款轻量的引擎。它的体积、文档还有活跃度上都非常不错,当然PIXI也有他的缺点,PIXI是一个较底层的游戏引擎,很多功能需要使用插件或者自己去实现,因为我们要做的游戏主要是以播放动画为主,再附带一些界面交互,所以选择了PIXI,这里大家在实际项目中可以列出自己关注的维度来进行对比挑选适合项目的引擎。

介绍完了骨骼动画的一些基础知识点,来看下我们QQ空间做的这款宠物游戏,这是一个养成类的游戏,主要功能包括喂食、偷糖果、换装、组合动作。还可以和你聊天,讲笑话,玩成语接龙,在空间主页也会显示这只宠物。大家可以下载***版的QQ空间手机版来体验这款游戏。

接下来介绍项目实践中几个技术点实现。我们宠物游戏核心功能之一就是换装扮,可以自由替换帽子、衣服、裤子、背部挂件,换装扮对骨骼动画来讲就是换附件,附件有普通的图片类型,还有蒙皮类型,普通图片PIXI支持了,但是蒙皮类型的没有,要实现蒙皮类附件的替换,最开始想到的方式,hack引擎从图集读取出来的附件信息,修改它的纹理指向换装之后纹理。再构造一个新的SPINE对象,这样虽然能实现需求,但是画面会有闪动。

为了实现更完善的方案,我们熟读了一遍PIXI代码,找到了更好的方式,pixi有一个使用canvas做纹理的接口,所以:把canvas代替png图片绘制,如果有换装,就覆盖canvas以前位置的图片。实现换装有两部分,***部分是在页面打开初始化的时候,需要给宠物穿上现有的装扮,首先将原始图集转成canvas,再将装扮图片插入canvas指定的位置,这个位置是在SPINE导出的配置文件里定义好的,然后再通过PIXI的接口把canvas转成纹理,接着用这个新的纹理替换掉附件的旧纹理,再渲染就行了。第二部分是在装扮商城里换装,只需要覆盖原来衣服在canvas上的位置,在刷新下纹理就可以实现无缝换装了,代码里是简单示例,***步清理canvas上的旧衣服图片,第二步把新的衣服图片画到canvas上面,***更新画布。经过这两部分,就能***地实现换装功能了。

第二个功能点分享GIF图,游戏分享出去有GIF图动起来转化效果更好。我们实现GIF主流程有三个大步骤。***步H5截取每一帧图片,然后传给客户端组装成GIF图同时压缩,***上传到后台发布分享。合成GIF图的也可以用纯JS方案或者后台方案,纯JS方案需要引入第三方库,而由于GIF图太大我们要进行压缩,压缩就涉及到很多像素对比运算,效率很低,需要10秒左右。后台方案,如果我们把图片截好之后传给后台,这中间网络传输耗时就会很大,所以我们选择用客户端帮助我们合并GIF图的方案。

我们来看下H5截图的逻辑,主要是从canvas中将每一帧的图片截取出来,因为我们页面上会播放这个动画给用户看,为了节省资源,决定利用这个已有播放动画的canvas。最简单的,播放动画的时候截取每一帧的图片,但是实现之后发现一些低端机型截出来的图片不完整,播放起来就像演示一样很卡顿,原因是这些低端机性能很差,导致截图的时候会漏掉帧数。所以我们采取了多轮截图方式,通过FPS和动画时长计算出需要截取的图片数量,将截取的图片按照计算出来的key值进行保存,这样就能保证截取动画的完整性。FPS我们在游戏中会有一份缓存,没有的话会再跑一遍计算FPS。然后通过FPS和动画时长可以计算出需要截取的张数,然后在动画播放过程中进行截图,截取的图片会通过key值进行保存,判断截取的图片数量达到预期后就结束截图,通知客户端进行合并。

这是通过新的方式截取出来的gif图,效果不错。这个方案的优势在于利用了页面中已有播放动画的canvas来实现,减少了额外的资源。在设计方案的同时考虑性能和业务场景,来制定更合适的方案。

接下来向大家分享一下开发过程当中遇到的问题点。***个问题在调试素材的时候我们发现有些动作会展示错位,它的肚子和衣服都会飘到天上去,我们发现这个问题发生时有共性,只有蒙皮类型的动画才会出现这样的问题。为什么会这样?我们定位源码,阅读他们的源码,找到了触发原因,同一个插槽上面有多个附件就会触发这个bug,PIXI引擎在定时器更新遍历插槽时间轴的时候,在region切换到mesh类型的时候或者mesh切到region的时候,引擎没有隐藏之前的附件,所以就会产生漂移。通过修改源码解决了这个bug,***版的PIXI已经修复了这个bug。

第二个问题发现部分机型播放蒙皮类动画会有闪烁的问题,当时离发布只有一天了,时间非常紧急。我们一方面查源码定位这个问题,一方面查是不是素材导致了这个bug。在定位问题时,我们发现用官方的示例代码在特定机器上也能重现,确定了是引擎自身问题,我们当时尝试切换PIXI到旧版本,发现在某个低版本这个问题没有,可以肯定是新版本PIXI某个代码改动导致了这个bug,当时时间紧迫,所以临时用了旧版本PIXI解决问题。但仍要搞清楚新版本为什么有这个问题,我们对比新旧代码,看到了是这行代码的缘故,这行代码到底做了什么事情呢?这个变量是控制是否使用系统的VAOS插件,只要用了系统的VAOS插件就会有问题。VAOS插件可以避免循环渲染中不必要的开销,也会改进我们做循环处理时的代码写法。但是它对性能的提升不大的,基本上可以忽略不计,主要作用可以让我们处理循环渲染的时候做一些写法上面写起来更加优雅,代码上PIXI给我们做好了封装,我们全局关闭就可以了。

我们小结一下刚刚讲得这些内容,两个功能点实现,实时换装和技术点截图。蒙皮错位和闪烁bug,一方面我们熟读源码,第二是与作者交流。熟读源码让我们更快定位到问题的症结点,与作者交流可以帮助我们找到***办法解决问题。同时我们解决问题的时候,同时朝着不同的方向去努力的。如果当时因为一些特殊的原因使用了临时方案的话,之后要找到问题的本质,用***方案解决它,避免今后某个时间点背负这些技术债。

接下来交流我们在性能优化上面做的几件事情。当时主要从CPU、GPU、内存三个指标上测量性能,刚开发完时我们CPU有40%,GPU占67%,内存增量有100兆,这个数据光看起来就很差,实际测试对用户影响导致手机耗电、发热、卡顿和崩溃。

 

 

先来看下为什么CPU和GPU占用会这么高?看图片中的人物,我将所有的骨骼和网格都显示出来了,骨骼动画每一帧都需要控制这些点的运动,做了很多矩阵运算,而且开启了gpu加速,所以只要运动,cpu和gpu的占用就会飙升,越精细的动作,它控制的骨骼和网格数量就越大,所以消耗也会越大

我们做了三个处理,***、减少每一帧运动的骨骼及网格数量,根据实际情况权衡的,减少太多动作就会不精美。第二、我们通过减少不必要的运动频率,例如把待机动作改成隔几秒动一次,减少持续性的消耗。第三APP切换到后台时停止动画,防止切换到后台动画一直播放导致APP崩溃。

做了这几点以后,我们待机时CPU占用到12%,GPU占到34%,基本上跟客户端的占用差不多了。

第二个性能优化,减少内存占用。内存的占用主要来自纹理图片、JS对象的占用,于是我们做了几个测试,***个测试是将两倍尺寸的纹理图换成单倍图,但是实际测试优化效果并不大,和预期不符,经过查证,原因是我们这个测试是用chrome的profile来测试的,profile只会检测js对象和DOM占用的内存,不会检测GPU内存,而纹理图占用的是GPU内存,所以这里检测不出来,这也是我们从普通web开发转游戏开发后的一个误区,没有关注到GPU内存的占用,后面会讲到gpu内存的优化。这里先看第二个测试,将动作数据进行精简,对比发现精简后的内存占用大大减少,于是我们对动作数据进行了拆分。要实现动作数据拆分,并且按需加载,只在用到某个动作的时候才加载这个动作数据,我们暴露了PIXI的这个私有接口,这样就能直接添加新的动作数据进去。动作数据为什么占这么的内存?我们宠物游戏的特色,用户可以通过组合不同的动作来生成新的动作,所以它的动作数据很多。最开始我们完整的动作数据有70个,文件大小有3兆,转化成JS对象以后占用的内存更多,拆分后的初始动作从70多个减少到4个,文件大小从3M减到200K,拆分之后,JS占用的内存从49M优化到了18M,而且因为配置文件体积也变小了,页面加载速度也提升了30%。

第三个性能问题,游戏里面有个排行榜,可以切换到好友宠物去偷糖果,测试中不停地切换好友,内存会不停地往上涨,表现出来的问题就是webview进程会崩溃。我们的好友数量一般都有上百个,多的有上千个,所以这个问题很明显。

浏览器内存分为这几块,JS、DOM、GPU内存、编译后的Code等。JS和DOM的内存占用可以通过chrome的Timeline和Profiles来分析,其他的则可以通过chrome的任务管理器来看。开发调试时主要用chrome工具,上线前测试的时候需要在APP里面看实际环境的数据,就要借助客户端相关的测试工具。IOS的话用xcode,安卓是用的我们公司内部自研的一个工具。经过测试,切换12个好友后,JS内存增长了18M,GPU内存增长了90M,非常的严重。

***部分JS的内存占用通过定位下来主要是附件、网格数据的增长。如果新建一个模型对象的话会有很多重复的附件数据,因此针对同一种宠物,我们复用他的模型对象,切换好友就相当于只是换装扮。切换之后的SPINE对象以及初始纹理都能够复用。JS的内存增量从18M优化到了5M。

我们再来看GPU内存,由于前面复用了模型,模型的纹理也能复用,因此可以减少GPU内存占用。在确定这些纹理不再使用后,可以手动执行PIXI的dispose方法主动释放纹理,GPU内存占用从90兆优化到了30兆,效果很明显。***我们从排行榜切换出来,销毁好友的宠物数据,回收内存。

我们总结一下这次的分享内容,性能优化上面针对CPU、GPU内存主要有三个方面,***减少待机动画的频率。将待机动画这种一直在播放的动画减少频率减少持续性压力。第二点动作、素材文件做成按需加载,一方面减少内存的占用,第二方面可以提高访问的速度。第三纹理和模型尽可能的复用,减少内存占用。另外使用临时方案或者我们迫不得已使用临时方案,也需要深究***方案,避免某个时点背负这些技术债务。阅读源码、与原作者交流能够更好帮助我们。发布标准的制定,使用参照物对比。我们***测试阶段的时候有找一些参照物的,比如说我们之前开发过普通的游戏,我们可以将这些普通游戏CPU、GPU占用,以及内存增量做一个对比。非常感谢大家,我的分享就到这里。

责任编辑:Jane 来源: 51CTO
相关推荐

2015-12-07 10:00:13

HTML5Loading动画

2015-11-11 15:22:27

h5工具

2015-10-08 08:48:44

HTML5canvas动画

2011-07-19 13:07:26

iOS4 HTML5 动画

2014-07-04 09:52:57

HTML5

2017-02-07 11:35:26

Android动画蜡烛动画

2013-05-28 15:35:47

html5多线程

2012-08-30 16:24:04

HTML5欧朋W3C

2012-11-07 09:43:58

IBMdw

2016-01-20 10:11:56

华丽CanvasHTML5

2014-07-22 10:58:33

HTML5jQuery

2015-01-13 11:19:19

2013-01-24 10:26:04

HTML5HTML 5HTML5的未来

2012-02-23 10:28:43

AppCanHTML5移动应用

2012-03-29 09:18:47

HTML5WEB

2015-06-10 10:18:27

WebAPP开发技巧

2012-02-23 14:24:53

傲游3HTML5

2013-10-21 15:24:49

html5游戏

2011-05-13 17:36:05

HTML

2012-03-31 16:05:15

HTML5WEB
点赞
收藏

51CTO技术栈公众号