Cocos2d-x+Lua游戏的优化总结

近期游戏准备出安卓版本,在安卓上的性能表现不佳。经过一周多的优化,在性能上取得了较大的提升。游戏采用Cocos2d-x3.2+Lua进行开发,以下将在渲染效率,CPU效率,包大小等方面进行总结。

渲染效率

纹理格式 – 运行效率 内存 包大小

  • 所有的图片都通过一个python脚本(调用TexturePacker的命令行工具)自动转换为RGBA4444编码的格式。然后判断当前平台为安卓时,将默认纹理格式转换为RGBA4444。
-- 安卓启用4444纹理
if targetPlatform == cc.PLATFORM_OS_ANDROID then
    cc.Texture2D:setDefaultAlphaPixelFormat(cc.TEXTURE2_D_PIXEL_FORMAT_RGB_A4444)
end
  • 以上的过程会发现一种比较「反常」的现象,就是转成RGBA4444的图片要比原来的图片要大。所以在脚本中不能单纯的转换,需要对比转换前后的大小,只转换变得更小的图片。
  • 还需要注意性能和表现的平衡。有些图片转成RGBA4444后看起来太糙,严重的影响了游戏的视觉体验。对此需要小心的针对处理。处理方法为:在这些纹理使用前将默认纹理格式设置为RGBA8888,当纹理使用后再将其设置为之前的默认纹理格式。 Alt textAlt text
    左图是没有做处理的游戏截图,可见相当不平滑的光线和背景。 右图是只针对背景和光晕的纹理设为RGBA8888处理,视觉体验一下子就回归了。
  • 压缩成RGBA4444格式的PNG图片,还可以用pngquant工具进一步压缩,而视觉体验肉眼感受几乎没有变化。这样可以进一步的减少包的大小。
  • RGBA4444的纹理内存使用量要比默认的RGBA8888小一半,所以可以很大的减轻游戏的内存压力。而且和PVR ETC等压缩纹理相比,可以完全共用一套代码,不需要写额外处理逻辑,同时兼容iOS和Android两大移动平台。所以我认为性价比还是很高的。

纹理剪裁 – 内存 包大小

  • 游戏采用CocoStudio来制作骨骼动画。CocoStudio导出的骨骼动画导出的图片默认大小是POT(power of two)。这其实会造成很多空白像素的浪费,这些空白像素不仅会让图片变大,还会增加纹理的内存。
  • 具体办法是:将导出的POT图片,经过美术或工具的剪裁掉多余的空白像素,使之变成NPOT(non power of two),然后修改一下plist文件中<texture>中的width和height值,经试验,对实际的使用是没有任何影响的。
  • 通过把所有POT格式的图片裁剪为NPOT,不仅可以缩减图片的体积,还能减少纹理的内存占用

DrawCall OverDraw

  • kanglai对整个包进行DrawCall和OverDraw分析后发现,消除场景内还保留着天空背景。而这层背景实际是玩家看不到,因为它完全被消除场景挡住了。但是它会带来全屏的绘制造成了全屏范围的OverDraw,而且带来了很多额外不必要的DrawCall。将其隐藏后,FPS在低端机上提升明显。
  • 需要把这些看不到的东西全部隐藏或移除掉,否则它们会造成OverDraw和不必要的DrawCall,增加了GPU的负担。
  • 隐藏的办法,有些童鞋喜欢将其透明度设为0,但是这样是不会降低DrawCall的。最好的方法是将其visble设为false
  • 还有就是场景内有很多细碎的东西,如果它们都是一张张散图储存的话,会使DrawCall居高不下,从而可能导致FPS下降。why are draw calls expensive?
  • 解决办法就是尽可能的将经常一起出现在屏幕上的小图合并成一张大图

CPU计算

  • 避免在循环内做重复的运算。因为如果计算值在整个循环内都不会变的话,那么每次循环都去计算就是浪费CPU周期,应该将计算结果缓存在循环外部。
  • 想办法避免开销大的函数(如:开方函数,三角函数),寻找简单运算的替代方案。如:距离的比较可以不用开方先求出距离,而是直接用平方运算进行比较即可。
  • 尽可能的避免同时多个的cc.RepeatForever。在低端机上发现在对较多对象调用cc.RepeatForever时,FPS下降显著。原因可能是每帧带来的计算,和因此频繁触发的Lua GC。而垃圾回收是一个十分耗费CPU时间的操作。
  • 优化算法,剪枝,去除冗余的计算。在游戏内的碰撞系统是这么进行优化的:原来对每一个球,会遍历整个空间的碰撞体和墙壁进行碰撞检测;优化后的算法是取球当前的坐标,转换为格子坐标,然后只取格子周边6个格子内的碰撞体和墙壁进行碰撞检测。效率提高了很多倍。
  • 避免频繁的开辟内存,对象最好实现复用。开辟内存也是一项很耗费CPU的操作,尤其是在移动设备上内存紧张时。对象能重用尽量重用(建立对象池)。Lua内的表能初始化大小,尽量先初始化大小,否则rehash的操作很费时。如何编写高性能的Lua代码?
  • 用效率更高的库来替换,比如用cJson来替换Lua json,用pugixml来替代tinyxml2。或者将效率低的模块尝试用更低级的语言进行重写。
  • 出包时,关掉所有的print语句。你们都知道输出到缓冲区的log有多卡。

其他

异步加载

  • 预测即将用到的纹理和资源,提前将其进行异步加载。这样能在用到时,可以减少掉纹理加载的时间,给玩家的感觉上会更流畅一些。

移除不用的库

  • 由于引擎使用的是Cocos2d-x 3.2版本,所以没有3.3带来的模块精简的功能。但是我们也可以自己去小心翼翼的移除掉游戏根本不会用到的模块。比如:物理引擎,3d模块,CocosBuilder spine等等。
  • 具体的方法是:通过adt的打包日志,分析有哪些库被编译进最终的so文件中,然后去项目内一个一个搜索这些库的名称。找到其对应的Android.mk文件,然后尝试移除掉无用库文件,然后尝试编译,确保游戏能正确运行。
关注微信公众号:timind

9 responses

  1. 有个问题想咨询下,第一个是我们打包的时候一般都是用脚本来打包的,这样的话如何能看到日志

    • 不知道你说的是打包日志还是运行日志。在命令行下运行打包脚本的话,是能够看到log的。看运行log,输入命令 adb logcat即可。

  2. 默认release下CCLOG会被干掉的,不过如果用CCLog就没辙了-。-

  3. 对于出包时,把print cclog关了。不是很理解,release模式下会有影响吗?

    • CCLOG不用管,宏已经关掉了,但是Lua的print会映射到CCLuaStack.cpp下的lua_print函数,还是会产生调用,所以release模式下请最好关闭之。我的做法是在Lua里创建一个cclog函数封装Lua的print函数,然后release模式下cclog直接return。

  4. 请问作为外行想学做简单的游戏当个爱好,练练逻辑啊解决问题啊和统筹规划能力啥的,那么cocos-lua哪个版本更适合我这种菜鸟理解?又或者有其他引擎更合适吗?
    至于我已有水平么……本科时通识性质的C++的课,学到的最复杂的似乎就是冒牌排序了……然后还要考虑到还给老师很多(选lua是因为我可不想碰指针……)
    先谢谢了

    • 如果你是游戏行业纯小白的话。
      我推荐你用Unity,相对于Cocos来说,Unity的工具链更完善,文档更规范些,社区也相对更活跃,而且完美融合3D和物理引擎,可以创造更多类型丰富的游戏。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注