Android篇:(关于一些移植前的准备工作我就不相信阐述了,我们引擎是C++写的,所以NDK这些都是需要准备的。)
1:说到移植游戏引擎到Android,可能有些人都不知道从哪开始,其实最简单的方式就是用官方的demo来改。我这里用的是NDK里面的hello-gl2。这里看可以到在JAVA上层有3个文件(分别是一个主Activity,一个链接调用底层JNI库的文件,一个是glview)。相当于一个管理进程,一个链接调用,一个窗口显示。JNI代码中负责真正的游戏逻辑和渲染。如果你是单列模式的话,这套工程非常适合你。下面说几点要注意的。
第1:在AndroidManifest.xml里面要记得添加opengles的版本,因为我这里用到的是opengles2.0以后所以添加了<uses-feature android:glEsVersion="0x00020000"/>。
第2:同样你可以在在AndroidManifest.xml里面看到他已经标明了是android:launchMode="singleTask"(单例模式)。如果不是单例模式请修改。
第3:在GL2JNIView可以看到init函数中可以看到EGL的配置,其中setEGLConfigChooser会让你配置一些东西(RGBA,深度缓冲,遮罩缓冲),这里遮罩缓冲我们没有用,所以传的是0。其它的都可以默认。
这里RGBA和深度缓冲有些硬件上配置是不同的,比如以前在模拟器上运行这个DEMO有个BUG就是你比如强行配置成ConfigChooser(5, 6, 5, 0, depth, stencil) )才是可以运行的。还有如果有些机子是不支持RGBA888的话配置也可能失败。
为了避免上述情况这里还可以有另外一种写法:setEGLContextClientVersion(2);就会根据硬件自动来选择配置。但如果你想优化这里,那么这里还是自己设置比较好。
第4: 在android4.0以前EGLContext只有一个而且在程序换到后台后,android会自动释放opengl的资源,并且删除这个EGLContext,然后切回来的时候再重新创建。这里问题多的一B啊 有木有。下面就是这个几个问题。
a:如果你让他自动删除跟以前的EGLContext的opengl资源,你根本不知道他删除了哪些opengl资源,并且是在什么时候删除的。所以完全像是瞎子乱撞一样 撞对了就对,撞错了就错。所以非常的不保险。关于这点虽然我测试过很多次是这样,但查阅了各种官方文档和说明,就是TMD的没告诉你这是为啥,连android主工程师也TMD就这点到处抱歉但说还没有找到好的解决办法,我去,这是赤裸裸的玩我们。资料是我以前查的,现在不知道更新没有。有知道的同学可以解答这个问题。
b:如果你自己手动删除相关的opengl资源,一定要注意保存以前的相关指针等东西,因为如果你连调用的指针也全部删除的话,你切回到游戏他会重新生成造成内存泄露,真尼玛坑爹。最最坑爹的还有就是TMD有些机子上我居然第1次恢复opengl的时候有些资源恢复了有些没恢复,我必须连续删除恢复2次才管用。
解决这些问题的时候小弟血都快吐完了,不知道是小弟学识不够,还是真的android就是这么坑爹。总之就是这么憋屈。
2:onSurfaceChanged有时候会调用2次有时候width和height会交换。。。。。。对于这种无语的问题 我已经不想说了。
3:在AndroidManifest.xml一定要根据自己的游戏来设置横竖屏的锁定。android:screenOrientation="portrait" android:configChanges="orientation
4:说到Android移植就不得不说到屏幕适配。根据第3条先设置好横竖屏锁定后。再来进行配置。我这里有2中方法给大家参考我觉得比较不错。
第1:先根据一套屏幕分辨率来进行游戏UI的布局显示,以这个分辨率为基础。如果要移植到其它分辨率的话就很简单的进行长宽比的缩放就行了。比如我按照1024*768的分辨率来配置的,那么我移植到800*480的时候,横向缩放比例RX=800/1024,所有UI的长*RX。同理竖向缩放比例RY=480/768,所有UI的宽*RY。这个方法的缺点就是UI会被拉伸,缩扁,可能不好看,但缺是所有分辨率通吃的方法。
第2:还是根据一套分辨率来配置游戏UI。移植到其它的分辨率屏幕的时候根据屏幕长宽比来配置,是Y方向填满,还是X方向填满。如下图:
整体的思想就是,保证原来分辨率的长宽比来进行X或Y方向的填满。比如说我配置的分辨率是960*640。也就是3/2的比例。那么所有分辨率都保证这个比例。那么800*480的分辨率的话就要进行Y方向填满。那么Y方向缩放比例就是RY=800/640。X方向填不满,那么要预留的黑边是好多呢?就是( 800- 960.0f*RY ) * 0.5f;也就是X起始点是从这里开始画的。X方向的缩放比例依然是RY。同理X方向填满也是一样的道理。这样做的好处就是非常整体美观,没有拉伸缩扁,因为你永远保持了你原来的屏幕比例来进行的。唯一的不好就是会留出黑边,当然黑边你可以用一个很好看的UI来挡住即可。
5:关于在Android中的stdarg是这样的
__builtin_va_list
__builtin_va_start(v,l)
__builtin_va_arg(v,l)
__builtin_va_end(v)
所以要记得用宏区分开。
5:下面说几条关于IO方面的问题。
第1:一般C++游戏的资源都是放在assets这个目录下的。这个目录下的资源会直接打包在生成出来的APK中,所以相当于这个目录下的资源是放在一个压缩文件中的,如果你要读取这个目录下的资源,那么就要用到类似libzip的库来帮你读取资源。在2.2以前我都是用的libzip.so来读取的。这个动态库可以在网上找到,读取的方法参考头文件里面的方法就可以实现。在Android2.3以后就不用这么麻烦了,API直接给了一个AssetManager来帮你实现这个要求,具体的方法可以参考NDK里面的native-audio。
第2:正如IOS平台一样Android也是分可写和可读目录的,据我所知的Android可写目录有2个一个是SD卡一个是data/data。SD卡属于外设了,我比较不推荐把数据存到这个目录中。但如果你把数据存到data/data又会消耗机子本身的内存,各有取舍。看大家的喜好了。
6:在音频方面,Android上我依然是用的openal来进行音频的播放和处理。这方面的BUG基本上跟IOS差不多可以参考IOS篇的。后来我使用了Android本身提供的opensles来进行音频的播放和处理。我发现比openal更好用一点。因为我使用到现在还几乎没发现什么BUG。难道是Android上的支持好?
7:由于Android的厂商众多,芯片一不一样,说几个我现在遇到的很诡异在不同芯片上的BUG。
第1:在i9000的powervr芯片上,如果2个片的Z值很接近,那么很容易出现Z-fight的现象,而且越在屏幕下边越明显。这种坑爹程度。。。。。已经不是我能接受的了。即使你用了glPolygonOffset也是不管用的。其它的机子带有powervr芯片也会出现类似的情况。唯一的解决办法就是让这2个片离的距离要比较远,不能太近。
第2:同样在powervr芯片上如果你的资源已经包含了mipmap,如果你这个时候再去gl里设置一次mipmap就会出现错误。
第3:在高通的芯片上,如果你的shader里面写了discard。并且是这样写的话。
if( alp > 某值) {
discard
}
else
{
gl_FragColor = vec4( base, alp );
}
那么游戏就会崩溃,猜测原因是先做了discard后,再设置gl_FragColor 就会有问题,在高通芯片的驱动上。因为在shader代码中if条件语句是必然会执行if和else2种情况的。这是shader的特殊性,所以条件语句都是很费的尽量少用。那么解决办法就是先把条件写反设置就行了。先gl_FragColor 再discard,判断条件也自己处理下就好了。
第4:在IOS篇也说过很多芯片是认识不到npot的贴图的,会显示出来黑块。所以一定要记得做2次幂贴图才是好的。
总结:经过很多机子测试,结果发现mali的芯片居然是最好用的,基本上没有BUG,而且性能反而超越了powervr和高通。这一点我是万万没想到。这只能说明几点,很多厂商根本激发不出来powervr和高通芯片的潜能。或者高通他们都在虚报数据。而mali的数据都是真实可靠的。
8:如果要在gl线程中调用显示一个Android自己的UI控件,一定要记得在主UI线程中显示,不然就会报错,只要是跟Android本身有关的UI控件或者是本身UI的变化等都需要在主UI线程去做。
9:在第1条说过我们有一个主的Activity比如GLActivity,当这个GLActivity跳到了其它的Activity去执行任务的时候,Android可能会把GLActivity挂到后台休眠,那么这个GLActivity再重新激活的时候就会重新进GLActivity的oncreate,并且重置状态变量,所以一定要记得保存GLActivity的状态变量等。
10:关于Android的编译也是有优化选项的哦,不过是要自己写到mk里面就是了,以前用的cygwin来编译的,现在eclipse可以直接编译c++了,并且用的是android自主的编译器也是会带来性能优化的。大家要多更新。正是由于现在可以直接编译c++了,也带来了可以在c++DEBUG断点的好处,而且还有opengl的监视,也会给你带来很大的好处,大家要多注意官方的文档。会给你带来不一样的感觉。
11:Android上面的限帧是我做过最难受的限帧,如果你是在java层用sleep来做的话,那就瞬间崩溃了,你会发现java的sleep非常不精确,这个时候帧数计算会完全乱掉。。。。所以最好还是在底层限帧,并且用linux的高精度的sleep函数来做,并且还要在引擎更新帧数的时候还要做特殊的处理,可以用一些高级限帧的方法,当然最简单的还是在做一些在一些插值计算来弥补时间的误差带来的帧数不稳定的情况。
12:Android上的触摸响应和IOS有明显的不同,在IOS中触摸响应优先级很高,并且你可以设置单点触摸模式,这样可以很好的屏蔽其他手指触摸屏幕带来的不必要的麻烦。在Android中,触摸就我看来又跟gl线程不是一个线程运行的,他会单独更新状态,而且他的触摸响应度并不是最高首先级的。这样就会出现一种情况,你会在你游戏的一帧中出现2次up和down(如果你点击的够快)。所以一定要注意这种情况的发生。而且Android现在都是默认的多点触摸了,如果你要做单点触摸一定要做类似IOS的屏蔽效果,不然会出现很多BUG。