苏打世界的流体灌装是如何实现的 – Liquidfun在Cocos2d-x-Lua中的应用

题外话

新作「苏打世界」已经上架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中,使游戏逻辑可以操控汽水的灌装,隐藏,销毁和回收。

将这个复杂的任务分解后,大致如下:

  1. 移植LiquidFun库到Cocos2d-x中,覆盖其原先的Box2d库
  2. 在C++层跑通,使LiquidFun的流体粒子能在Cocos2d-x正常工作
  3. 对LiquidFun做tolua绑定,使Lua中使用LiquidFun成为可能
  4. 在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逻辑实现

  1. 构建好仓库和瓶子的物理形状
  2. 填充好粒子系统到仓库中
  3. 用按钮来控制仓库出口开关的位移
  4. 生产按钮按下时,仓库出口开关移走,粒子以重力掉落到瓶子中
  5. 生产按钮恢复时,仓库出口开关回原位,粒子将被其阻隔而不能下落
  6. 当粒子掉到屏幕外时,销毁该粒子,并在仓库内重新创建一个新粒子

一张图让你明白,其实我们后面是真的在汽水!

Alt text

性能优化

GPU

  • 尽可能只使用一个RenderTexture来渲染所有粒子,以避免重复绘制的浪费
  • 如果可能,限制用于渲染流体粒子RenderTexture的大小,避免全屏绘制,以降低绘制面积
  • 精简shader,移除没必要的变量和多余的if...else...分支判断
  • shader使用低精度浮点数即可:precision lowp float;

CPU

  • 将耗时操作放在C++层执行,以避免每次循环内调用tolua的开销。比如从C++层直接获取一个在屏幕外的粒子列表,而不是在Lua层对所有粒子循环判断其位置。因为每次粒子位置的获取都需要消耗tolua时间,粒子数一多,就容易积少成多拉低性能了。
  • 粒子对玩家不可见时,将粒子系统的更新暂停,直到下次被使用时再恢复,可以极大缓解设备发热和耗电量
  • 降低Box2d世界更新的步进值,以达到性能和效果的平衡
  • 在保证效果的情况下尽可能减少同屏共存的粒子数
  • 自己管理粒子的生命周期,以避免在每次粒子的步进更新中检测是否销毁
  • 使用release版,掩面逃)

参考资料

8 responses

  1. 我是提姆绿,你能解释一下为啥现在有时候粒子是方形的吗?!

    • 因为移除无用纹理时,粒子的纹理被释放了。。。现在已经好了。。。

  2. 大神,请问如何在cocos2d-lua中使用您的tolua后的C++文件?给个提示!

    • 你好,你可以多花点时间去了解下Cocos2d-x是怎么使用tolua的,怎么使用绑定的tolua文件由于太基础不在本文讨论范围内,希望你自己花点时间来搞懂。

      给个提示:在Appdelegate.cpp里引用了一个头文件lua_module_register.h,这里面就注册了所有的tolua绑定,你可以看看Cocos是如何做的,如果需要使用我提供的tolua的绑定C++文件,只需照猫画虎即可。

  3. 你好,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,多用用搜索引擎就好。

发表评论

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