题外话
新作「苏打世界」已经上架AppStore,并在首页被大屏推荐。上架伊始便颇受玩家喜爱,目前已经收获大量玩家的五星好评,欢迎大家前去下载试玩。
本文将介绍游戏内的灌装系统是如何实现,最后将附上参考资料以供读者们阅读和研究。以下将不会有具体代码,只讲思路和方法。
缘起
在「苏打世界」里,一个很重要的日常任务便是生产苏打。由于产品汪提姆绿需要尽可能的去模拟真实的苏打灌装,比如苏打灌满后溢出和未灌满时水面的波澜起伏等,这也导致用动画来模拟实现是不可能的。
所以便只能去寻找一个支持流体功能的2D物理引擎,并将其移植到我们开发采用的Cocos2d-x + Lua的游戏框架中。
LiquidFun
当接到这个需求后,我便联想到了Google的LiquidFun。
由于LiquidFun支持JavaScript,这也非常方便我们快速验证原型,即LiquidFun到底是否合适我们的游戏。
对LiquidFun的testbed修修改改后,我快速的建立一个灌汽水的网页版原型。同事们看完觉得还可以,便开始了将LiquidFun移植到游戏的工作。
怎么接入?
LiquidFun依赖于2D物理引擎Box2d,可以认为其是Box2d的加强版,比原先的Box2d增加了流体粒子功能。
我们游戏采用的是Cocos2d-x+Lua的框架,所以问题变成了:如何整合LiquidFun到Cocos2d-x中,并绑定其常用API到Lua中,使游戏逻辑可以操控汽水的灌装,隐藏,销毁和回收。
将这个复杂的任务分解后,大致如下:
- 移植LiquidFun库到Cocos2d-x中,覆盖其原先的Box2d库
- 在C++层跑通,使LiquidFun的流体粒子能在Cocos2d-x正常工作
- 对LiquidFun做tolua绑定,使Lua中使用LiquidFun成为可能
- 在Lua书写灌装的逻辑,实现一个灌装的DEMO
如何移植到Cocos2d-x
关于LiquidFun的移植工作,已经有先驱帮我们趟坑了。Cocos2d的作者Retro在其博客中详细阐述了:如何将LiquidFun移植到Cocos2d-x,以及如何利用metaballs技术来渲染粒子使其看起来更像流体。
读者不妨先去阅读以上的两篇文章,花点时间,搞懂每一步的目的和原因。
简而言之,Retro在Part1所做的工作是将LiquidFun的每个流体粒子在物理世界的坐标转换到Cocos2d-x的绘制坐标中,然后用GL_POINTS
命令将粒子绘制成一个个的小点显示到屏幕上。
在Part1内,我们已经完成了LiquidFun对Cocos2d-x的C++层的移植工作。但是仅仅显示一个个白色的小点是不够的,因为这样看起来不像流体而比较像沙子。
接下来在Part2中,Retro引入了一张中间圆形实心,四周趋于透明的纹理代表一个粒子,利用一个透明度阀,将此透明度(例如0.01)以下的全部显示为全透明,而之上都显示为一个纯色。然后用一个RenderTexture去实现整个Shader的绘制,最后将其显示在屏幕上。当两个粒子很临近时,可以看出一个不规则的融合形状,这个技术就是最基础的metaballs。
C++绑定到Lua
由于Cocos2d-x没有对Box2d进行tolua绑定,所有绑定的工作需要从零开始一遍。
原先本想采用Cocos 3.x引入的ini格式的配置进行绑定,后来发现不太现实,因为有太多的结构体需要进行绑定到Lua。
所以只好回归Cocos 2.x时代的pkg绑定。关于如何进行pkg绑定的方法,不是本文关心的内容。因为基本都是把C++头文件按照指定的规则转译为pkg文件的体力活。
我曾经在2.x时代因为极度痛恨这种手工方式而写了一个Lua脚本来进行自动转译,这个脚本现放于Github中,年久失修,可能有坑需要手填,慎用!
这基本是个体力活,为了大家移植方便,不再趟坑,我已经将转译好的pkg文件和tolua后的C++文件上传到Github的仓库中,大家可按照MIT协议进行下载使用。
Lua逻辑实现
- 构建好仓库和瓶子的物理形状
- 填充好粒子系统到仓库中
- 用按钮来控制仓库出口开关的位移
- 生产按钮按下时,仓库出口开关移走,粒子以重力掉落到瓶子中
- 生产按钮恢复时,仓库出口开关回原位,粒子将被其阻隔而不能下落
- 当粒子掉到屏幕外时,销毁该粒子,并在仓库内重新创建一个新粒子
一张图让你明白,其实我们后面是真的在灌汽水!
性能优化
GPU
- 尽可能只使用一个RenderTexture来渲染所有粒子,以避免重复绘制的浪费
- 如果可能,限制用于渲染流体粒子RenderTexture的大小,避免全屏绘制,以降低绘制面积
- 精简shader,移除没必要的变量和多余的
if...else...
分支判断 - shader使用低精度浮点数即可:
precision lowp float;
CPU
- 将耗时操作放在C++层执行,以避免每次循环内调用tolua的开销。比如从C++层直接获取一个在屏幕外的粒子列表,而不是在Lua层对所有粒子循环判断其位置。因为每次粒子位置的获取都需要消耗tolua时间,粒子数一多,就容易积少成多拉低性能了。
- 粒子对玩家不可见时,将粒子系统的更新暂停,直到下次被使用时再恢复,可以极大缓解设备发热和耗电量
- 降低Box2d世界更新的步进值,以达到性能和效果的平衡
- 在保证效果的情况下尽可能减少同屏共存的粒子数
- 自己管理粒子的生命周期,以避免在每次粒子的步进更新中检测是否销毁
- 使用release版,掩面逃)
参考资料
- LiquidFun Programmer’s Guide – 官方文档
- Integrating LiquidFun with Cocos2d-x: Part I – 在Cocos2d-x中显示流体粒子
- Integrating LiquidFun with Cocos2d-x: Part II – 利用metaballs技术使粒子更像流体
- Inside LiquidFun – LiquidFun技术原理
- Make a Splash With Dynamic 2D Water Effects – 2D流体效果的实现原理
我是提姆绿,你能解释一下为啥现在有时候粒子是方形的吗?!
因为移除无用纹理时,粒子的纹理被释放了。。。现在已经好了。。。
大神,请问如何在cocos2d-lua中使用您的tolua后的C++文件?给个提示!
你好,你可以多花点时间去了解下Cocos2d-x是怎么使用tolua的,怎么使用绑定的tolua文件由于太基础不在本文讨论范围内,希望你自己花点时间来搞懂。
给个提示:在Appdelegate.cpp里引用了一个头文件lua_module_register.h,这里面就注册了所有的tolua绑定,你可以看看Cocos是如何做的,如果需要使用我提供的tolua的绑定C++文件,只需照猫画虎即可。
好的!谢谢您的回复!我去学习下!
强制绑定手机。。不绑还不给领取奖励呢
你好,liquidfun的Programmer’s Guide好像只有c++的?你们的javascript版本的api是在哪里查的呢?
访问官网http://google.github.io/liquidfun/,你就能看到它的testbed就是用JavaScript写的。
wget -r http://google.github.io/liquidfun/
就能把它download下来,然后自己改改就行了。具体怎么build,看这个https://google.github.io/liquidfun/Building/html/md__building_java_script.html,多用用搜索引擎就好。