七匣子logo
手机游戏 > 手游新闻 > 骨骼动画于空间萌宠H5上的实践与调优

骨骼动画于空间萌宠H5上的实践与调优

作者:SilvaReta  2017-07-14 14:01:07

转载自腾讯GAD:http://www.gad.qq.com/article/detail/28923
 
1
 
·         初识入门

什么是骨骼动画,本篇先简单做下科普,其他大家自行百度哦

比帧动画:它比帧动画大幅节省了资源空间,也比帧动画对手机性能有更高的要求,webgl下能达到最佳的展示效果

编辑器选择:业界比较主流的骨骼动画编辑器有SPINE和DragonBones(龙骨,egret白鹭公司在维护)。我们联系到cp使用的SPINE编辑器比较多,而又需要同样一个资源文件,三端公用(ios,安卓,H5)SPINE的运行库选择更多,所以我们选用了SPINE编辑器,虽然都是骨骼动画,但是他们的动画原理是略有差别的。

运行库选择:SPINE官网上有各种语言运行库的推荐,单js有10余种运行库,到底选哪个游戏引擎,工欲善其事必先利其器,选择很重要。选择恐惧症的话还是在科学的量化的方式选择更适合我们项目的游戏引擎。主要有性能,是否支持webgl,库体积,论坛活跃度,API健全度等5个维度综合分析后,选择了PIXI引擎。附件会附上对比表格。PIXI的使用上比较便利,官网上有丰富的例子让你熟悉它的使用,或者你可以读我们项目组成员nicholguang写的初识PIXI初步了解下,这里不做详述

·         深入了解骨骼动画原理

在新接手这个项目之前,公司km上还鲜有骨骼动画H5的实践,听了IEGT4专家david分享的游戏开发经验,虽然没有提到H5上的经验,但是他建议的熟读游戏引擎源码很是受用,熟读代码既能让你实现功能的时候更得心应手(一下就找到最优方案,而不是不停返工),也能在性能优化时有的放矢,所以就这样,一手api,一手源码开始了空间宠物的开发。
先让我们解开骨骼动画神秘的面纱吧~纯手画给大家奉上我对于骨骼动画的理解,这里仅仅是SPINE编辑器下的原理,不同的编辑器有略微的不同,这里不多余说明。如果当时有人给我这张图,能节省我1天工作量,呜呜~
 2
 
图解:
1.骨骼动画中的人物是由有骨骼,插槽,附件部分组成,这三部分都是1对N的关系。
    (a)骨骼是一个树状结构,有个明显的好处就是,如果动画时要设置位移,只要设置根节点的位移,整个任务都能一起位移。
   (b)附件其实人物外表的展示,主要有三种类型:图片,蒙皮,权重蒙皮。图片好理解,就好像是一张贴花贴着,很僵硬,就像下面左边的那个长枪,右边就是蒙皮,可以定义形变,让整个长枪动得更加自然流畅
                        4                           5           
                                         左图                                                                       右图

为什么蒙皮能自由形变呢?因为它有顶点,边缘,三角区域这三个概念,能对图片某个区域变形,这中特性在webgl是原生支持的,但canvas2d下是引擎自己写的,这个就是说骨骼动画对性能要求更高就是因为这个。为什么这三个概念能自由变形呢?看下面的图解
6

7
只要移动了那个顶点就能拉长鼻子

2.骨骼动画中的动画,是基于时间轴的,定义某个时间点显示那个附件,骨骼的位移和旋转等等,如我手画的下图所示
 
8
 
3.引擎播放骨骼动画流程:
(1)引入SPINE编辑器导出的json文件
(2)引擎自动引入同名的atlas和png文件
(3)解析json和atlas文件,生成spine对象
(4)加入到容器里面
(5)定时器渲染,播放动作
 
10
 
4.其他,主要的就上面3点了,其他的自行了解咯
·         空间宠物关键技术实现
1.实时换装:
换装功能跟附件息息相关,图片类型的附件引擎自带换装方式,但是蒙皮类型的附件却没有!
最简单的方式,hack引擎从atlas读取出来的附件信息,修改它的texture指向换装之后texture(webgl渲染用的纹理)。再new 一遍 Spine对象,这样虽然能实现需求,但是画面会有闪动,体验不好
所以在熟读了一遍PIXI代码之后,找到了更优雅的方式,pixi有一个Texture.fromCanvas的接口,可以把一个canvas作为一个纹理绘制,所以:把canvas代替png绘制,如果有换装,就用canvas的clearRect擦除和drawImage覆盖以前位置的图片(需要注意的是旋转这个参数)。bingo,换装功能完成,换装时就不再闪动了,而且图片类型的附件还是使用引擎自带换装方式,更快!流程大致如下:
 12
经验教训:实现代码之前务必熟读引擎源码,理解原理流程,打通任督二脉,找到最佳实现方法。

2.组合动作编辑  用户对于宠物不同部分的动作自由组合,形成特色动作
 14
实现原理:
animation.state['setAnimationByName'|'addAnimationByName'](track, act_name, loop, delay);
 可以基于轨道track(int)来做动作的叠加
 16
亮点:
(a)实现播放动作有限次
(b)维持一个播放队列

3.快照分享
 17
问题1:webgl截图空白
答:
(a)WebGL获取上下文时,有一个关键参数:preserveDrawingBuffer,默认为false,表示在绘图完成后不保留绘图缓冲区。如果设置成true,会影响性能
(b)在定时器里面,同步截图,DONE!

问题2:毛玻璃效果怎么实现?需要重新引入库来解决吗?
答:否,pixi引擎自带支持哦,Container对象底下有个filters参数可实现毛玻璃效果

3.分享gif,因为我们本来是骨骼动画,如果要分享出去必须截图再合成gif。
方案1:单次播放法,满帧截取的情况下,ios能达到要求,但是安卓上不同机型,不同机器现状都可能截出不同效果的gif,差的时候只能2,3张,效果差而且不稳定
方案2:多次播放截图法,我们通常用的定时器计数的方式比较多,但是浏览器每轮播放状态无法做到一致,所以计数截图法不可取,所以就要用到计时的方式,所幸requestAnimationFrame里面回调会传过来一个time,引擎会转化当前动作已经播放了多长时间t,我们可以通过t来截取
 18
返回:由Math.floor(t*fps)生成的index截图组成的数组;最终的截图数组长度 >= fps * 动画总时长就停止截图  最终的效果也是很赞的
 20
这种截图方式损失的时间,那会不会截图的时间过长呢,我针对截图时间做了下统计:
动画的平均长度为1.8s,两端的截图耗时
IOS : 2025ms
Android:4535ms
这个分享页面需要用户填写心情之类的,4s以内的截图耗时可以接受

·         引擎优化

第一次用PIXI,第一次用PIXI-SPINE。作为一个要上线承载上亿用户的产品,开发过程,遇到了一些引擎水土不服的地方,主要有:

1.播放动画的时候展示错乱,脸部五官漂移
 21
定位:是因为同一个插槽下面有多种类型的附件
解决:看源码时,发现引擎在定时器更新遍历插槽时间轴的时候,在region切换到mesh类型的时候或者mesh切到region的时候,引擎没有隐藏之前的附件。所以就会产生漂移。之后如果还要展示之前的附件只需要重新设置可见性即可。修改代码
 if (type === spine.AttachmentType.region)
        {  
             if (slot.currentMesh) {
                slot.currentMesh.visible = false;
                slot.currentMesh = undefined;
                slot.currentMeshName = undefined;
            }
....
}
if (type === spine.AttachmentType.skinnedmesh || type === spine.AttachmentType.mesh || type === spine.AttachmentType.linkedmesh)
        {  
            if (slot.currentSprite) {
                slot.currentSprite.visible = false;
                slot.currentSprite = undefined;
                slot.currentSpriteName = undefined;
            }
....
}
 最近看了下PIXI-SPINE的最新版本这个bug已经fix了。

2.画面闪动,无法正常显示动画
定位:有mesh且在某些机器上才能重现,比如nexus5。这些机器并没有明显的共性。把webgl渲染强行切成canvas2d的,显示就正常了,说明还是webgl下mesh某种特性兼容性的问题
解决:通过google搜索,和自己debug。发现是因为一个参数引起的,如果设置成true,出问题的机器上就正常了
PIXI.glCore.VertexArrayObject.FORCE_NATIVE = true;
 这句话的意思,如果是false的话,就用每种浏览器对于创建VAO(顶点数组对象)的扩展(之前有提到过,mesh是有顶点,三角形区域这样的组成)
if(!VertexArrayObject.FORCE_NATIVE)
    {
        this.nativeVaoExtension =              gl.getExtension('OES_vertex_array_object') ||                                gl.getExtension('MOZ_OES_vertex_array_object') ||                                  gl.getExtension('WEBKIT_OES_vertex_array_object');
    }
 

3.增加图片超时逻辑
解决:PIXI加载图片的逻辑就是先声明一个img,img.onload之后再触发loaded事件,业务再去处理依赖于这个图片的逻辑,没有超时逻辑,会一直等待。页面就是不可点击的状态,对于一个成熟的产品必须有很高的可用性,所以必须有完善的错误兼容的逻辑!就在源码里加上了超时逻辑,业务能正常执行

·         性能调优

功能开发好不容易完了,兼容性也OK了,但是性能却挺糟糕,crash,发热,进入游戏慢,性能bug单狂轰滥炸,静下心来各个击破,最终项目各个性能指标达标,在外网稳定运行。
工欲善其事必先利其器,定位性能问题,要通过工具去分析哪里是性能瓶颈,才能有的放矢,虽然通过chrome的性能分析工具已经能发现大部分问题,但是ios和安卓上因为实现的差异是不是存在其他的问题,也需要测试一下才放心。这里搜罗了一下常用的性能分析工具,供大家参考
 22
1.crash
原因:页面内存占用过高是主要因素,什么资源最占用内存呢,通过chrome profiles面板分析便知
 24
54%的内存都消耗在动作的timeline数据上了。用排除法分析了一下一个动作的json占用的内存数
 26
每个动作原始数据+解析成数组总共占用390KB。每个用户每次用的动作有限,并不需要把完整的动作数据加载出来,所以解决crash可以通过一下方法
(a)首次只加载模型json以及必须的2个动作数据,其余的按需加载,解析完塞到宠物的动作数据里面(这里需要改写源码暴露读取动作数据的接口)
效果:内存减少:49M减小到18M,减少了80%(chrome上测试)
(b)ios上启用wkwebview,大幅提高webview的稳定性。QQ空间app上只要链接上加usewk=1即可启用

2.发热
原因:发热跟CPU和GPU占用息息相关,webview作为一个比较高层的应用,对cpu和gpu的占用是要比原生app的占用高很多,这应该也是H5游戏发展的瓶颈所在。减少资源大小,将canvas里面固定的图片独立出来等等措施都收效甚微。只要webgl在requestAnimationFrame里面不停渲染就会发热严重
解决:降级策略看起来这里唯一行得通的解决方案了,没有渲染就没有发热。所以就要把渲染用在刀口上。损失非关键体验,安卓停掉默认动作停止渲染,需要时再打开渲染,改成隔3秒播放一次动作让用户感觉也是在一直动
效果:有效解决手机发热的问题
 28
 
3.帧耗时-fps
PIXI是支持自动识别浏览器是否支持webgl来选择是canvas2d还是webgl来渲染动画,先科普下webgl的市场占用率吧
ios: ios7.1以上都是支持WebGL的
android: 安装了tbs的机器支持WebGL
因为tbs是热更新的,新安装的APP没有tbs,就不支持WebGL,而又是我们推广的关键时期。所以canvas2d下的渲染也需要做好它的优化。
解决:canvas2d下的帧耗时的优化可以通过将canvas2d中不变的背景独立到dom上,不放在canvas里面渲染
效果:优化了40%左右。举证材料都会在附件里面
 30
 
4.游戏启动速度优化
解决:
(a)不采用一般游戏进入先loading资源,采用背景和宠物初步加载的方式
效果:背景可见时间减少4s 8.4->4.2s
 32
(b)动作JSON按需加载
效果:宠物可交互的时间减少了2.8s  8.4->5.6s
 33
 
5.WebGL内存泄漏
表现:安卓上黑屏,ios上因为用了wkwebview会重新loading
定位:在排行榜频繁切换好友的时候会必现,排行榜的设计模式是每个好友都是独立的,每次都销毁上一个宠物,再添加下一个宠物,这是面向对象编程基本的思路。
 35
可是这样切换几次后就会异常,直觉告诉我是跟WebGL或者内存有关。这里先科普下浏览器的内存占用分几块:
 37
js heap和Dom内存的占用通过chrome的profiles和timelines面板可以看出来,但是其他的内存占用可以在哪里看呢?通过chrome的更多工具 -> 资源管理器即可!
39 
 
有了这个强大的工具,问题定位将不是问题,其实主要是两个问题
(a)js内存上涨,切换12个好友js内存上涨了18M,定位下来增长的内存是附件mesh等等信息
(b)GPU内存只升不降,GPU内存就是WebGL占用的内存,如果有独立显卡的话他的内存占用是和浏览器内存独立的,但是手机端没有独显,可能会跟浏览器有共享内存,这个按机型而异。但是GPU内存增长太多绝不是好事,它可能会影响浏览器申请更多的GPU,造成页面黑屏,也会导致浏览器占用内存过多,被原生app杀掉或者重启。所以这里就是频繁申请释放GPU内存造成的内存泄漏

解决:既然已经发了问题,解决方法就应运而出啦
(a)同一种宠物模型对象复用,切换下一个好友的宠物就是换装,解决js内存过度增长!
效果:切换12个好友内存增长了5M而已
 41
(b)换装纹理复用,不销毁。因为换装纹理是一个canvas,完全可以擦除之后再回收再利用。解决了GPU的内存泄漏
效果:
以前的方案,5次切换好友之后,GPU内存直接飙升了90M  166M到255M
 42
复用纹理后,5次切换好友,GPU只上涨了30M,第二次切换后并没有上涨  166M到199M
 44
(c)从排行榜切换回来之后,销毁好友的宠物模型。这个是PIXI官方提供的销毁接口。
pet.stage.destroy(true)
 销毁后,GPU内存恢复到最初状态 170M左右
 46
经过上述三次改造后,内存就维护在一个比较稳定的状态,黑屏和reload的问题就修复好了

·         总结

1.做游戏,需要熟悉引擎源码以及WebGL。越熟悉越好
2.项目需要的内存越多,能够运行它的终端就越少,所以一定要想方设法的定位内存占用大头,逐个击破
3.可以使用降级策略,降级策略要能因机制宜,切忌大刀阔斧。根据机器的剩余内存和GPU核数或者当前的FPS值来做降级策略
 

七匣子声明:七匣子登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。

长按二维码进群领取七果独享福利 手游充值福利群

推荐礼包 换一换

变态版手游推荐

换一组

更多游戏>>
分享到: