上一期主要谈到资源大小的优化。纹理大小和声音大小除了会造成包体,内存和带宽之外,加载纹理和声音的时间也是一个优化的重点,在游戏中,当切换进入一些比较复杂的场景时,很有可能会造成游戏的卡顿。针对纹理加载耗时卡顿问题,还是拿Cocos2dD-x
举例,提供如下两种解决方案:纹理缓存和异步加载。
纹理缓存
纹理缓存,顾名思义就是提前将图片缓存到内存中,而不是需要使用时再从硬盘加载。Cocos2D-x
为我们提供了SpriteFrameCache
纹理缓存类,利用SpriteFrameCache
类中addSpriteFramesWithFile
成员函数将图片读入内存中,读入过程会完成图片的解压缩(当图片的格式需要解压缩时,如上期提到的PVR格式)、数据读入和数据传到GPU的过程,然后用set来维护在缓存中的图片集合,利用set特性保证缓存中图片的唯一性,不会加载重复的图片,从而保证游戏的运行效率。
// 添加到纹理缓存中
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("xxx.plist");
纹理缓存也是一把双刃剑,在提升效率减少耗时的同时,缓存的图片势必要消耗内存,从而增加游戏内存管理上的压力。一般做法,会在切换场景时释放当前场景缓存的图片,来保证游戏资源占用的内存一直在一个安全范围内。这种处理方式也有一个漏洞,如果这两个场景有共同的图片,会存在先释放后马上读入同一张图片的情况,这样不但没有节省内存,也浪费了运行效率。解决方案可以将游戏中需要用到的图片分为两类,只在某个场景使用和在多个场景使用的公共图片,公共图片比如ui列表中使用的按钮等,一般很多场景都会使用,这种图片就可以“常驻”内存,在游戏启动时就加载公用图片列表到缓存,然后一直存储在缓存中。只在特定的场景使用的图片,在进入该场景时加载到缓存,退出场景时释放即可。
另外在Cocos2D-x
中进行图片内存释放时,很多开发者误以为调用SpriteFrameCache
类中removeSpriteFramesFromFile
成员函数传递plist文件名字就会删除这个文件的图片数据,实际并非如此,这个操作仅仅解除了图片的引用,要删除图片还得调用TextureCache
类中的removeUnusedTextures
成员函数。注意调用先后顺序,先解除引用再删除。这里特别强调一下,removeSpriteFramesFromFile
和removeUnusedTextures
需要前后帧调用,因为引用删除后才会删除它们引用的图片。这里我建议在当前场景调用removeSpriteFramesFromFile
解除引用,在下一个场景调用removeUnusedTextures
删除。
// 当前场景:从精灵帧缓存中解除文件引用
SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("xxx.plist");
// 下一个场景:删除未使用的纹理
TextureCache::getInstance()->removeUnusedTextures();
异步加载
什么时异步加载?异步加载说的是利用多线程,在读入某些比较大比较多的图片时,不等待读入后返回,而是边读入边做其他操作,这样做就是为了消除在进入场景时带给玩家的卡顿感,提升游戏的流畅度。
在Cocos2D-X
中,通过调用Director
导演类中的TextureCache
类实例对象的AddImageAsync
成员函数完成。
Director::getInstance()->getTextureCache()->addImageAsync("Images/HelloWorld.png", CC_CALLBACK_1(TextureCacheTest::loadingCallBack, this));
总结,游戏的性能瓶颈就是设备的内存空间与运行时间,在优化时需要考虑的就是当前项目是否有浪费的内存空间和运行时间。当空间和时间都得到充分利用以后,剩下的就是平衡空间和时间哪个更重要,纹理缓存就是空间换时间的一种方式,对于空间的释放则是时间换空间的方式,而异步加载,则是在二者中取平衡,给用户一种已经加载完了的错觉,当真正加载完的时候再进行界面二次刷新。
当然,随着如今的设备性能越来越强,很多开发者往往忽略性能优化这项非常重要的工作,希望更多开发者关注性能优化,制作更多高效的精品。中国的精品游戏还得靠大家。