转载

苏打世界的流体灌装是如何实现的 – 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. 当粒子掉到屏幕外时, 销毁 该粒子,并在仓库内 重新创建 一个新粒子

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

苏打世界的流体灌装是如何实现的 – Liquidfun在Cocos2d-x-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流体效果的实现原理
原文  http://wuzhiwei.net/liquidfun_in_soda_world/
正文到此结束
Loading...