Nurture是我们为准妈妈专门准备的app。在app首页有一个炫(dāi)酷(méng)的婴儿动画。这篇blog就来聊一聊这个动画是怎么实现的以及我们走过哪些坑。

如果你还没有亲手体验过这个动画,你可以戳下面的链接下一个感受一下:

动画格式

婴儿肢体动画的制作使用的是 Flash 并导出为 LWF格式。这样做的优点是对于设计师来说,所设计的动画在最终的产品的效果基本上是完全一样的。

LWF 官方提供了基于 UIKit 的渲染器,所以在 iOS 上工作完全没问题。不过因为其并没有直接提供 Android 的支持,所以我们在 Android 版本的开发中就遇到了坑。

尝试过的方案

我们尝试过下面几种方案:

  • 使用视频文件代替。 但是因为支持透明通道的视频格式不多,而且转换出来效果也不好,所以最后并没有使用这个方案。
  • 使用 Pure2D。 Pure2D 是一个纯Java的游戏引擎,而且LWF提供官方支持(因为是一个人写的)。不过这个引擎的文档和成熟度都让人很着急,最后不得不放弃。
  • 使用 Cocos2d-x。这是一个很成熟的游戏引擎,做过游戏的小伙伴们应该都听说过它的。LWF 提供官方支持,不过因为是 C++ 的引擎,所以在整合的时候需要多费一些力气,因此我们一开始并没有选择这个方案。

我们最后选用的是最后一个方案。

对于上面的三个方案,它们都有这么一个大坑:

要显示视频,需要用到 VideoView;而直接绘制 OpenGL 内容则需要用到 GLSurfaceView。他们都是 SurfaceView 的子类。因为一些原因(或许是 Android 系统的限制),SurfaceView 如果不放在整个 View Hierarchy 的最上面(通过设置 setZOrderOnTop) 就不能够提供一个带透明部分的 View ,但是程序中很多地方其它 UI 控件是会需要把动画遮住的。所以我们只能够把显示动画的 View ,放在 View Hierarchy 的最底下。但这就带来另外一个问题,就是用户自定义的背景图片就不能简单地通过一个 ImageView 放在动画下面来实现。我们的解决方案是通过 JNI 将自定义的背景图片传给游戏引擎,再由游戏引擎将图片渲染出来。具体做法戳这里

Cocos2d-x

改造

Cocos2d-x 提供了 Cocos2dxActivity 来承载整个游戏。但是我们其实希望展示动画的界面是一个 Fragment 而不是一个 Activity,这样动画界面才不会对我们原本的程序有太大的侵入,同时也方便我们在动画上面放上其它的UI控件。因此对其进行一些改造。

具体的来说,我们创建了 AnimationFragment ,里面基本照搬了 Cocos2dxActivity 中的方法,同时把之前需要 Cocos2dxActivity 类型作为参数的改成了需要 Context 或者 Fragment ,比如将 Cocos2dxHandler 的构造函数从 Cocos2dxHandler(Cocos2dxActivity activity) 改成了 Cocos2dxHandler(Context context) ;同时移除了一些我们不需要的部分,比如和 Video, Webview,Dialog 这些功能相关的代码。你可以在本文后面提供的 demo 中找到这些更改后的代码。

线程安全

我们需要从 Java 中通过 JNI 告诉游戏引擎现在需要显示第几周的动画或者其他信息。直接调用看起来是可以的,动画确实也会做相应的响应,但是 Java 的 UI 图片资源却时不时的会消失掉。经过分析,我们认为这是因为我们在非 OpenGL 的线程进行了不安全的操作。

为解决这一问题,我们实现了一个简单的消息队列(通过 std::function 以及 std::bind )。我们在 JNI 调用时将需要进行的操作放入队列,在游戏每一帧的回调中去检查并执行。

优点 VS 缺点:

这样的实现其实并不算是一个完美的解决方案,但是基本上解决了我们的需求。
它的优点有:

  • Cocos2d-x 有完善的社区和文档,遇到问题心里不慌。
  • 可以使用 Xcode 或者 Visual Studio 开发动画的部分。至少从模拟器的启动速度上来说,效率是很高的。

当然缺点也很明显:

  • 由于是游戏引擎,同一时间在界面上只能出现一个 Cocos2dxGLSurfaceView 。 同时由于 Fragment 的生命周期问题,有些转场也会显得不自然。
  • 在生产环境中如果遇到 C++ 代码中的 Crash 很难收集和处理。

Demo

最后,你可以克隆这个巨大的 Demo。里面使用 LWFFragment 实现了一个简单的婴儿动画,同时可以看到动画和其它UI控件在一起的效果。需要在真机上运行哦~