游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

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

发布时间:2012-08-08 18:00:03 Tags:,,,,,

作者:Charilaos Kalogirou

当你为全世界的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用户也来玩我们的游戏吧!(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

In-depth: Porting your game from iOS to Android

by Charilaos Kalogirou

So you created a C/C++ game for iOS that gives joy to iPhone and iPad gamers from around the world. How can you deny this joy from loyal Android users? I can’t, so I had to port Pop Corny to the Android platform. It was a very interesting experience, full of gain as I say, and I think it would be nice to share some information and knowledge on the subject.

The basics

First of all, if you are feeling comfortable in your xcode environment and are enjoying the feathery wings of mother Apple, get prepared for a rough landing to the Android land. Be prepared to face lots of not-so-streamlined tools, and basically no documentation

The NDK (the toolchain and libraries you need to build native apps on Android) has no relation to the SDK. It is obvious that Google is working hard on bringing native code support to the platform, but we are not at the place where developing native code is as nice as it is with Java.

The first thing that you must get comfortable with are the build tools and process. Google exposes the same tools that are used for building the actual Android platform to the NDK developers, and by tools I mean a set of shell scripts and makefiles.

What you are actually being requested to do in order to build your project is write a makefile part that gets included in the main makefiles provided by the NDK. This causes a steep learning curve at the beginning that might demoralize some. However, when you get the grasp of it, it works fine and it is probably better that rolling out your custom makefiles.

In the end, what you are ultimately building is a dynamically linked library that Dalvic can load with JNI. That is right, your game will still be a Dalvic Java VM process that just calls out to your library.

Mixing Java and native code

So you will not be able to fully escape Java. Your code must get along with it, and this is actually something you want, as almost all of Android API’s are still Java only. Also most libraries for Android that you might want to use are also written in Java. For example, if you want Openfeint for leaderboards and achievements, of Flurry for analytics, you need to talk to Java.

This is accomplished with Java Native Interface (JNI). This interface allows Java code running in the VM to call out, and to be called back, by native code written in C/C++. This is an example of how the code to call out to Dashboard.open() from native code.

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

The only dark point in the above code is the “()V”, which is the internal type signature of the method. This is the way that the Java VM describes the parameters and return value of a method.

The syntax is error prone, and I suggest you always use the “javap -s myclass” command that prints out all the methods along with their signatures. Copy and paste from there. Keep in mind that if you misspell a signature, you will only find out at runtime.g

Even though that the latest versions of the NDK allow you to write an activity in full native code, I went with the traditional way of creating the activity in Java, and then doing the calling out to native code from that.

Input

Handling touch input is slightly more complex on Android than on iOS, as the designers of Android thought it would be cool to have the system pass in an array of “historical” touch events instead of calling you for each. Apart from that, the story is the same. Just make sure you handle both ACTIONUP and ACTIONPOINTER_UP events.

The major issue, however, that also applies to many other aspects of the porting details, is that these events come in on a different thread. This might surprise some iOS developers that are accustomed to almost everything happening on the main thread’s looper.

It did surprise me at least, but it turns out that Android is very generous with threads. So depending on how your engine is coded, you might have to queue up the events, and then pass them to your native code from the thread it expects them.

Finally, there is button — real hardware button — handling. You would want to handle at least the back and home button in the way Android users expect them to work.

Sound

This is where the Android platform took me by surprise… Brace yourself… there is no OpenAL! It was one of those things that you can’t believe, and you keep looking desperately, denying the simple truth.

So it is true, if you are hoping to easily port your OpenAL-based sound engine to Android, you are in for a big disappointment. I believe it had to do with some licensing rights or something. The choices you are left with are MediaPlayer, SoundPool and OpenSL ES. The first two are Java APIs while the third is native.

MediaPlayer is basically for playing music and sounds that don’t require low latency. I could have used that for playing music, but I decided to try OpenSL. I implemented the music playing part of the engine on OpenSL and decided that I don’t like the API. If I knew from the beginning, I would go straight for MediaPlayer, which is very straightforward.

The SoundPool class is very well suited for playing sound effects. It also does the sound decompression for you and stores the uncompressed-ready-to-play samples in memory.

It has its drawbacks as it can’t support effects that are bigger that 1MB in most of my test cases. The SoundPool class also has a bad history behind it. Due to a race condition in the code, SoundPool caused every app that used it on the first dual core phones to crash! Mainly the Samsung Galaxy S2 with the vanilla Android version.

Can you imagine that? You have your nice game running on the store, and one day a company releases a phone that causes your game to crash… and sells millions of it! The fix from Samsung came a year later. Until then, game developers had to drop SoundPool and probably implement the same functionality in OpenSL ES — which I tell you is not fun.

The worst part is that even now that Samsung released newer versions of Android that don’t have the problem, most of the users don’t upgrade. So even last month, when I released Pop Corny, most S2s had a buggy SoundPool. I made the decision not to drop SoundPool and simply detect when running on a buggy version and don’t play sound effects at all.

Graphics

Thank god Android does support OpenGL! You will have no problems here. Just be a little careful with the multi-threaded nature of Android and you will be okay (all GL commands must come from the GL thread).

But you must be prepared for the variety of resolutions that Android phones and tablets have. You are no longer in the iOS ecosystem, where you can get away with just two aspect ratios (iPhone and iPad).

For Pop Corny, the game already supported the aspect ratios of iPhone and iPad, so I just generalized and made the code accept a certain range of aspect ratios, and after that add black bars as necessary.

For example, the exotic resolution of 480×854 pixels on some phones is so extreme that it can’t be handled without redesigning the whole game. Therefore it gets black bars.

It will also be useful to only load the appropriate texture mipmap and below, depending on the screen resolution. This will save precious memory especially on the low-end devices that usually come with the low resolution displays.

The major problem that you are going to face with OpenGL when porting to Android is dealing with the activity life cycle. As you probably already know, everything on Android is an activity. Even a small dialog box that you will bring up is an activity.

The problem is that when the dialog comes up, it pauses your current activity and if that activity was your OpenGL view, Android will trash your OpenGL context!

What that means is that when the dialog will go away, to get back to rendering you will have to reload every resource that OpenGL had. The same applies for when the user puts your application in the background, or when the user takes a call mid game.

Loading all your textures again, whenever something like that happens, is unacceptable. This one took me a while to sort out. Possibly due to the fact that I had no actual Android device to test on and I was relaying on the slow beta tester round trip.

Anyhow, it turns out that this was fixed on version 3.0 of Android. The GLViewSurface of that version adds a method named setPreserveEGLContextOnPause(boolean) that when set to true, it tries to preserve the GL context.

But as you knownvery few people upgrade on the Android ecosystem. So what I did was take the GLSurfaceView class from the latest sources of Android, make some changes, and use that instead of the one in the user’s phone. Simple as that.

However, even with that, many phones were losing the GL context. It turns out that the GLSurfaceView did not preserve the context in the case the GPU was an Adreno regardless of whether the GPU supported multiple contexts.

Well, all Adreno-based devices I tried can preserve the context, and simply removing that test in GLViewSurface’s source allows the game to continue instantly after an activity pause. Case closed.

Assets

The final thorn in the porting endeavor was the asset management and loading. Those that come from iOS will be surprised to find out that Android does not decompress the application bundle when installing an application, like iOS does.

The files will remain in the .apk file, which is essentially a zip file. This causes a number of problems. You can’t just use your trusted system calls to open a file and read it. You have to open the apk file and poke in it, find your file, decompress it, and then use it.

For some files you can skip the decompression part. There are some kinds of files that the build process stores uncompressed in the apk. Mostly media files that are already compressed. If you use ant for building, you can actually add more file extensions to the no-compression list.

Unfortunately, I didn’t manage to find a way to do it with Eclipse. These files can be loaded easily (with the usual file manipulation functions) using the file descriptor of the apk, an offset, and a length that you can get from the Java AssetManager.

In the case of a compressed file, however, you will have to load it completely in Java using the asset manager, and copy the whole file data over to C using JNI, which is inefficient.

Thankfully, Google added native asset loading capabilities after version 2.3. So if you are only supporting 2.3 and up, you can forget all the above and use the native API directly. It does all the work for you.

Closing words

As you can see, the Android platform has its quirks. Most of the time due to the NDK still being too young. It is getting better, though, with every new version. If only Android users were quick to upgrade to the latest version…

To all of the above, add that you probably want to compile for three different CPUs: ARM, ARM7 and x86. That’s right, x86. There are quite a few tablets out there that are based on x86 right now, and we are probably going to see even more with time.

Endianess might also give you some trouble if you weren’t so careful with it when originally developing the game for iOS. Not because of actual differences in the endianess, but mainly from depending on iOS/OSX specific C defines for detecting it.

It might be a little cumbersome at times, but the effort really pays off. In the end, you have a completely new world for your game to explore. And the Android users are also very welcome and warm. A lot more than iOS users, I think. So let’s give them our games!

Good luck!(source:gamasutra)


上一篇:

下一篇: