Charilaos Kalogirou:分享将iOS游戏移植到Android平台的注意要点

当你为全世界的iPhone和iPad用户推出基于C/C++的iOS游戏时,你怎么忍心让忠实的Android用户无法享受同样的乐趣?我不能,所以我得把《Pop Corny》移植到Android平台。这是一次有趣的经历,让我受益匪浅,所以我要把我的心得体会分享给读者。

pop corny(from harryballs.com)

pop corny(from harryballs.com)

基础

首先,如果你安于xcode、苹果生态圈的舒适开发环境,现在准备飞向那片叫作Android的大陆,那么,请准备迎接困难和挑战吧,因为Android提供的工具并不那么合理,且基本上没有文件编制。

NDK(开发Android的本地应用的工具链和类库)和SDK(软件开发工具包)没有关系。显然,为了使原生代码支持Android平台,谷歌已经很努力了,但我们开发原生代码还是不如用Java来得便利。

生成工具和进程非常重要。谷歌给NDK的开发者提供的工具,与构建Android平台的工具相同,这里的工具我指的是一套壳脚本和生成文件。

为了生成你的项目,你要做的是编写生成文件部分,这部分包括由NDK提供的主要生成文件。这使用户的学习难度增大,一开始就可能吓倒一些人。然而,当你掌握它以后,你就会觉得还不错,之后构建你的自定义生成文件时,你可能会觉得更好了。

最后,你建成的是一个动态链接库,Dalvic可以用JNI(Java的本地界面)装载它。对啊,你的游戏仍然是调用库的Dalvic Java VM进程。

混合Java和本地代码

所以你不能全完摆脱Java。你的代码必须与之兼容,这其实正是你想要的,因为几乎所有Android应用程序界面(API)仍然只用Java编写。另外,你可能想使用的大多数Android库也是用Java编写的。例如,如果你想采用Openfeint的排行榜和成就功能,使用Flurry分析工具,你就必须与Java打交道。

这是用Java Native Interface (JNI)完成的。JNI使在VM中运行的Java代码可以被用C/C++编写的本地代码调出和调回。以下是代码如何从本地代码中调出Dashboard.open() 的例子:

jclass cls = javaEnv->FindClass(“com/openfeint/api/ui/Dashboard”);
jmethodID open = javaEnv->GetStaticMethodID(cls, “open”, “()V”);
javaEnv->CallStaticVoidMethod(cls, open);

以上代码唯一的问题是”()V”, 它是类函数的内部类型的署名。这是Java VM描述参数和类函数返回值的方法。

这种语法很容易出错,我建议你始终使用”javap -s myclass”指令,它将所有类函数与它们的署名一同输出。从那里复制和粘贴。记住,如果你拼错了一个署名,你就只能在运行时发现。

即使最新版的NDK允许你用全本地代码写一个活动,我仍然会按老方法在Java中写活动,然后从那里调用本地代码。

输入

在Android上,处理触摸输入比在iOS上更复杂一点儿,因为Android设计师认为有一个直接调用一系列“历史”触摸事件的系统比较酷,而不是让你挨个调用。除此之外,其他都是一样的。你只需要确保你用了ACTIONUP和 ACTIONPOINTER_UP事件。

然而,存在于移植细节的许多其他方面的大问题是,这些事件是不同线程的。这可能会使一些iOS开发者感到吃惊,因为他们习惯于让几乎所有事件都发生在主线程循环中。

至少我是很意外,Android对线程非常大方。所以你要根据自己引擎的编码方式来排列事件,然后将它们从相应线程中传送到你的本地代码。

最后,还有按钮,即真正的硬件按钮——触摸。至少是后退键和主按键,要确保它们符合Android用户的操作习惯。

Apple Android(from 2-soft.com)

Apple Android(from 2-soft.com)

声音

这是Android让我吃惊的另一点。请做好思想准备——居然没有OpenAL!你一定难以置信,一脸绝望,不敢接受这个事实。

但这就是真相。如果你希望轻松地将基于OpenAL的声音引擎移植到Android,恐怕你要大大地失望了。我认为这跟某些版权有关。所以,你能选择的只有MediaPlayer、SoundPool和OpenSL ES了。前两个是Java API,第三个是本地API。

MediaPlayer基本上是用于播放不需要低延迟的音乐和声音。我本可以用它播放音乐,但我决定尝试OpenSL。我试过OpenSL的引擎的音乐播放部分后,觉得不喜欢它的API。如果我一开始就知道,我就会直接选择非常简单的MediaPlayer。

SoundPool非常适合播放音效。它还帮你解压了声音,在内存中储存未压缩的现成样本。

但它还是有自己的缺陷,在我的测试中,它不能支持超过1MB的效果。SoundPool之后还有一个很糟的历史记录。因为代码的紊乱情况,SoundPool会让所有在第一代双核手机中运行它的应用程序崩溃!最为典型的就是运行vanilla Android版本的三星Galaxy S2。

你能想象吗?在店里,你的游戏运行得好好的,但有一天,让你的游戏崩溃的手机卖出了数百万台!三星在一年之后才解决了这个问题。从那以后,游戏开发者不得不放弃SoundPool,在OpenSL ES上执行相同的功能。我跟你说过了,OpenSL ES并不好玩。

最坏的是,即使是现在,三星发布的更新版本Android不会有这样的问题了,但大多数用户都没有更新操作系统。所以甚至是在上个月,当我发布《Pop Corny》时,大多数三星Galaxy S2的SoundPool还是有漏洞。我决定不放弃SoundPool,在运行有漏洞版本的SoundPool时进行简单的检测,并且完全不播放音效。

图像

谢天谢地,Android确实支持OpenGL!这下没问题了。但你还是要小心Android的多线程特点,这样就没事了(所有GL指令都必须来自GL线程)。

但你必须准备好对付各种Android手机和平板电脑的分辨率。你不再生活在iOS的生态系统中了,所以你要解决的不只是两种高宽比(iPhone和iPad)的问题了。

对于《Pop Corny》,游戏已经支持iPhone和iPad的高宽比了,所以我只让代码接受某个范围的高宽比,之后增加必要的黑条。

screen-sizes(from gamasutra)

screen-sizes(from gamasutra)

例如,某些手机拥有480×854像素的古怪分辨率,不重新设计整个游戏居然就不能解决这一问题。所以,游戏在这些手机上显示的是黑条。

只载入适当的MipMap或更低级的纹理,也非常有用,但这取决于屏幕的分辨率。这会节省宝贵的内存,特别是对于低端设备,因为它们的屏幕分辨率低。

当移植到Android时,你遇到的OpenGL主要问题是,处理活动生命周期。你可能已经知道了,Android上的任何事件都算一个活动。即使是一个小对话框也是一个活动。

问题是,当对话框出现时,它就会中断你的当前活动,并且如果那个活动是你的OpenGL视图,Android就会消除你的OpenGL活动!

这意味着,当对话框消失后,要返回你刚才的活动,你不得不重新载入所有OpenGL的资源。当用户后台运行你的游戏时,或当用户在游戏运行时打电话,相同的问题出现了。

每次都要再次载入所有纹理,这是无论如何也不能接受的。我想了好一阵子才想出解决办法。这可能是因为我没有Android设备做测试,所以我只能依靠低beta测试器反复测试。

无论如何,3.0版的Android最终解决这个问题了。那个版本的GLViewSurface(游戏邦注:GLSurfaceView的作用是使用户能更容易更好地使用OpenGL渲染应用程序)加了一个名为setPreserveEGLContextOnPause(boolean) 的方法,当开启时,它就会保存GL活动。

但你知道在Android生态系统中很少人会升级操作系统。所以我要做的就是,从Android最新资源中获取GLSurfaceView的类,做些调整,然后使用,而不是使用用户手机中的。就这么简单。

然而,即使是那样,许多手机还是丢失了GL活动。结果是,当GPU是Adreno时,无论GPU是否支持多活动,GLSurfaceView都不能保存活动。

好吧,我尝试的所有基于Adreno的设备都可以保存活动,只要移除在GLSurfaceView的资源中的测试,使游戏在活动中断后继续进行。

资源

移植大业的最后一个障碍是,资源管理和载入。使用过iOS的人会很惊讶地发现,当安装程序时,Android居然不会像iOS那样解压程序包。

文件仍将保持.apk状态,但它实际上是一个zip文件。这引发了一连串的问题。你不能只是用自己信任的系统打开文件并读取。你必须打开apk文件,然后挨个寻找你的文件,解压,最后再使用。

对于某些文件,你可以跳过解压部分,即某类构建过程储存未压缩成apk的文件。大多数媒体文件都已经压缩了。如果你使用ant构建,你其实可以在无压缩的列表当中添加更多文件拓展名。

不幸的是,我对Eclipse(游戏邦注:著名的跨平台自由集成开发环境)没办法做同样的事。使用apk的文件描述符、字符补偿和长度(可以从Java资源管理器中获得),可以轻松地载入这些文件(使用常用文件处理功能)。

至于压缩的文件,你却不得不用Java资源管理器完全地载入,然后使用JNI将所有文件数据复制成C语言,这样效率会很低。

所幸的是,继2.3版本之后,谷歌加强了本地资源载入能力。所以如果你的设备只支持2.3或以上版本,你可以忽略以上问题,直接使用本地API。它会帮你解决所有问题。

总结

正如你所见,Android平台有它自己的缺陷。大多数时候是因为NDK还不够成熟。不过,随着新版本的发布,它会越来越完善。当然,Android用户最好能勤快一点地更新版本……

对于以上所有问题,你可能想编译三个不同的CPU:ARM、ARM7和x86。现在仅有一些支持x86的平板电脑,但假以时日,我们还会看到更多这样的平板电脑。

如果你原本是开发iOS游戏,但移植到Android版本时不认真处理的话,字节顺序可能还是会给你带来一些麻烦。但这不是因为字节顺序的不同,而主要是因为会检测它的iOS/OSX特定C语言定义。

有时候会有一点儿麻烦,但努力总会得到回报的。最后,一个全新的世界等着你的游戏去探索。Android用户也非常热情友好,我认为会比iOS用户还更热情得多。所以让Android用户也来玩我们的游戏吧!

via:游戏邦/gamerboom.com

感谢支持199IT
我们致力为中国互联网研究和咨询及IT行业数据专业人员和决策者提供一个数据共享平台。

要继续访问我们的网站,只需关闭您的广告拦截器并刷新页面。
滚动到顶部