说一下需求,因为最近AR有点火,大BOSS从手机淘宝发现了一个AR游戏,在“我的淘宝”右上角
有一个“抓喵喵”的游戏,好像是AR实现的(本人小白,不确定是不是AR。。。囧!),点进去就是一个游戏,如下图:
所以大BOSS发话了,也要弄一个出来,就集成到我们自己的App里,于是开干。。。。。。先介绍一下大体功能,点击进入游戏,先是一个加载页,这个我没上图,就是一张介绍游戏规则的图片加一个进度条,说一下游戏场景里的功能,首先是一个地图,看左下角。。。。。高德地图 这里用到了地图和定位功能,主角定位的位置就是我当前位置,主角少女旁边有很多怪物(喵喵),点击喵喵,就进入捕怪场景了(这些就属于游戏部分了,具体功能大家可以下载或更新手机淘宝体验一下),抓捕到喵后就能获取优惠券折扣之类的。
本来的安排是游戏这块是交给Unity开发做的,我们移动端只需要在app上留个入口,然后加载Unity游戏就行,所以任务下来了后就了解了一个Android和Unity交互这块的知识点,这个网上太多了,我这里就不多说了,主要说一下,在嵌入Unity过程中遇到的各种问题
Android 中嵌入Unity
关于Android和Unity集成,网上有两种方案,一种是把Android打包成jar,集成到Unity中,Unity中还要引入Android的SDK,另一种就是把Unity打包,然后解压,把相关的jar包、资源文件引入Android项目中,我采用的是第二种,具体步骤我就不写了(主要是懒。。。。),找了两个,大家可以看看 (交互步骤两位作者都写的十分清楚,赞!):
说一下我的问题,Unity开发大哥,写了Demo,打包发给我,我照着网上的步骤先试着往项目里集成,一切都很OK,很轻松。然后问题来了,本来这个游戏场景是Unity做的,但是Unity那边集成地图出现了无法解决的问题,所以地图这块儿需要移动端来做了,我有点忐忑,因为以前从没集成过地图,于是咬着牙硬上了,花了一天时间(囧!!!),把地图弄出来了,刚开始用的是百度地图,但是要求中心点不在地图正中心(中心点不在地图中心还叫中心点吗?),弄半天没弄出来,然后iOS哥们用的高德地图,可以把中心点偏移出中心位置,于是建议我也用高德,好吧,换吧,正好统一下,换成高德地图,OK,都弄好了,开始往里面集成Unity了,如下简单布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<com.amap.api.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.amap.api.maps.MapView>
<FrameLayout
android:id="@+id/fl_unity_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
<ImageView
android:id="@+id/iv_unity_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/unity_loading"
android:scaleType="fitXY"/>
</RelativeLayout>
这里的mapView就是高德地图,FrameLayout 就是Unity场景的父布局了,最后的那个ImageView就是最开始游戏加载的图片。当前的Activity须得继承UnityPlayerActivity,代码:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catch_layout);
//高德地图
root = View.inflate(CatchActivity.this, R.layout.activity_catch_layout, null);
mMapView = (MapView) findViewById(R.id.mapView);
mMapView.onCreate(savedInstanceState);// 此方法必须重写
//Unity 父布局
fl_unity_layout = (FrameLayout) findViewById(R.id.fl_unity_layout);
//把Unity游戏加载进来
fl_unity_layout.addView(mUnityPlayer.getView());
//游戏加载图片
iv_unity_loading = (ImageView) findViewById(R.id.iv_unity_loading);
//下面省略地图初始化和定位设置。。。。
}
我按上面代码跑了一下,发现Unity场景怎么也出不来,之前单独加载Unity可是OK的,咋回事?而且我的Unity可是放在地图上面的,后来查了一下,地图的MapView和Unity都涉及到了OpenGL中的glsurfaceview,所以冲突了,想具体了解的可以看看OpenGL和glSurfaceView,于是我把这里的MapView换成了TextureMapView,解决了Unity不显示的问题
Unity背景不透明问题
解决了Unity不显示问题后,出现了另一个问题,那就是Unity场景的背景色是一片漆黑色的,即便Unity那边设置了背景透明,打包后集成到Android中后也是漆黑一片,这就坑了,于是Android和Unity两边找解决办法,各种找答案
https://forum.unity.com/threads/transparency-on-android.456736/
https://stackoverflow.com/questions/17705364/unityplayer-as-a-subview-with-transparent-background-unity-game-engine
http://www.geekyhamster.com/2013/07/unityplayer-as-subview-with-transparent.html
硬着头皮看了几篇英文文章,都遇到了这个问题,发现只有Unity开发版本降到4.2才行,这叫一个坑!!!没办法,现在Unity开发大哥的Unity开发版本是2017的,最后把版本降到了4.9,没解决,再降到4.2,搞到了半夜,还是没弄出来,回家。。。。。第二天早上,稍改了一下,试跑了一下,居然可以了,当时激动的,这个问题搞了两天,终于好了。。。。解决办法是参照上面第三个链接改好的,但是只能Unity 4.2版本打的包有效果,其他版本都不行,操蛋!!!
Unity-class.jar 混淆问题
解决了背景透明问题,心情大好,想看看集成了Unity游戏后apk有多大,于是准备打个release包看看,但是,一打release包就报错,但是debug包没问题啊,也能直接在手机上运行,错误如下:
Warning:Exception while processing task java.io.IOException: Can't read [D:\****\app\libs\untiy-classes.jar(;;;;;;**.class)] (Can't process class [org/fmod/FMODAudioDevice.class] (256))
在网上查了一下,好像是混淆的时候出的问题,在build.gradle 文件里一看,debug版没有进行混淆,所以没有问题,但是release混淆就出问题了,所以,很明显,就是untiy-class.jar 这个包混淆时出问题了,于是照着网上的方案,将proguard-rules.pro 文件里的混淆规则改了一下
-dontwarn com.unity3d.player.**
-dontwarn org.fmod.**
-dontwarn bitter.jnibridge.**
-keep class com.unity3d.player.** {*;}
-keep class org.fmod.** {*;}
-keep class bitter.jnibridge.** {*;}
再打包。。。。还是报错,还是这个问题,摔桌。。。。。
再搜。。。。
找到了遇到这个问题的解决方案:
android引入unity-classes.jar之后进行混淆的问题解决
作者遇到的问题和我的简直一毛一样啊,兴奋。。。。。照着作者的方案,把ProGuard 的源文件改了,使用ant 重新编译了,然后再打包。。。。。还是报一样的错,再摔桌。。。。。
再发几个相同解决方案的链接:
http://www.cnblogs.com/huangbei1990/p/6097782.html
http://bbs.csdn.net/topics/392084419?list=lz
http://blog.csdn.net/vinomvp/article/details/58614043
http://blog.csdn.net/tianyutaizi/article/details/41698933
https://sourceforge.net/p/proguard/bugs/420/?page=0
https://stackoverflow.com/questions/22165902/proguard-returned-with-error-code-1-proguard-errors-with-untiy-classes-jar
http://glblong.blog.51cto.com/3058613/1435941
https://sourceforge.net/p/proguard/bugs/420/
然而,都没解决我的问题,有点小绝望了。。。。
最后仔细想想,应该是版本的问题,几位作者的帖子大都是三四年前的了,可能版本更新后,有的问题就不能照原办法解决了,问题卡这儿了。。。。
这个打包的问题还没解决,另一个问题又来了,4.2版本的Unity打包的程度在app上陀螺仪失效了,在这个游戏里,用户是可以拿着手机移动方位的,效果就是地图也会随着屏幕旋转,而且游戏中的人物也会随着屏幕旋转的,比如,上图中,有一只喵在少女的后面,没显示出来,需要用户拿着手机转个身,将身后的怪物显示在屏幕当中,这里的Unity中有陀螺仪效果,喵喵会随着屏幕旋转而出现/隐藏于屏幕中,在iOS中是可以的,但是到了Android里就不行了,地图可以旋转,但是Unity中的模型动不了。。。。。
android端开发顿时卡住了,而Unity开发大哥因为android这方面的问题就先顾iOS去了,先把iOS搞出来,再来解决android问题
替换方案
android这边两个问题最终还是没解决,一个是4.2版本的打包问题(2017版Unity的可以打包),另一个就是陀螺仪失效问题。。。。
最后技术老大拍板拿了方案,地图和怪物显示都android来做,Unity只负责打怪场景(Unity开发版本用2017版),当然,只是android端。。。苦逼
只能在地图上动手脚了,要在地图上显示怪物,只能看自定义 Marker,我是拿到当前位置的经纬度,然后通过随机数,在当前位置上随机增加或减少经度和纬度,将怪物分布到当前位置的附近,再设置MarkerOption来显示怪物的图片
//随机生成经纬度
double demonLongitude = currentLongitude + ((random.nextDouble() + 0.01) * (random.nextInt() > 0.5 ? 0.002 : -0.002));
double demonLatitude = currentLatitude + ((random.nextDouble() + 0.01) * (random.nextInt() > 0.5 ? 0.002 : -0.002));
LatLng latlng = new LatLng(demonLatitude, demonLongitude);
MarkerOptions markerOption = new MarkerOptions().icon(BitmapDescriptorFactory.fromResource(demonList[db.getMonster_id()-1]))
.position(latlng)
.draggable(true);
aMap.addMarker(markerOption);
当然这个效果和iOS端用Unity实现的效果比起来就差多了,但是我暂时没其他解决办法了。。。。一个是怪物不能动,不像Unity实现的那样能够动而且是3D效果的,地图上只有一张图片,第二个就是图片还是固定大小的,不能设置,想把怪物显示大一点都不行,若是哪位同学对高德地图API里设置marker这块比较熟悉的话望告知,谢谢
Unity退出问题
这里还有个坑爹问题,就是Unity退出的时候,会将整个进程杀掉,导致app重启。。。。
Unity中退出使用的是 mUnityPlayer.quit() 方法,但是我们看看这个方法的代码:
public void quit() {
if(this.r != null) {
this.r.b();
}
this.o = true;
if(!this.e.e()) {
this.pause();
}
this.a.a();
try {
this.a.join(4000L);
} catch (InterruptedException var1) {
this.a.interrupt();
}
if(this.g != null) {
this.l.unregisterReceiver(this.g);
}
this.g = null;
if(j.c()) {
this.removeAllViews();
}
//这里是关键
this.kill();
g();
}
关键代码是在倒数第三行 this.kill(); 下面是kill()方法:
protected void kill() {
Process.killProcess(Process.myPid());
}
杀死进程啊,有木有。。。。。所以APP就重启了啊。。。。
网上千篇一律的都说加个按钮调用mUnityPlayer.quity() 方法,或是在onBackPress方法中调用mUnityPlayer.quity() 方法,这样和直接调用 mUnityPlayer.quity()有什么不一样么,都重启app了,或许我用的方法不对?
稍靠谱点的就是android端调用Unity方法,Unity端执行退出,另一个方案就是将Unity单独放另一个进程里,这样退出的话就不会杀死app所在的进程了。
但是这两种方案我都试过了,第一种,Unity端退出执行的还是quity() 方法,最后还是把进程给杀了,第二种试了也没效果。。。
最后想了个笨方法,就是用户点返回按钮或是退出这个继承 UnityPlayerActivity的Activity时,不将这个Activity 关闭,而是将这个Activity的启动模式设置为singleInstance,即每次打开的时候将其单独放在一个任务栈里(因为app主页是singleTask模式,进入主页时,会将其上面的activity都清除出栈,为了避免UnityPlayerActivity子类被清除,所以将其设置为singleInstance,作为一个单例),这样这个包含Unity游戏的Activity就不会finish掉,同时也解决了每次加载Unity游戏时时间过长的问题,就第一次加载时花费一些时间,下次就不用再花时间来加载了。。。。。我们Unity加载时间得几十秒钟,得等十一过后,Unity开发大哥来解决这个问题了。
其他要注意的问题
1、Unity调用Android是在子线程中运行的,所以如果涉及到UI操作,记得切换到主线程
/**
* 供Unity端调用
* 接收Unity传递过来的数据
*
* @param MessageData
*/
public void getUnityMessage(int messageType, String MessageData) {
switch (messageType) {
case 2://获取怪物列表
runOnUiThread(new Runnable() {
@Override
public void run() {
//隐藏加载中图片和Unity场景,显示地图
iv_unity_loading.setVisibility(View.GONE);
fl_unity_layout.setVisibility(View.GONE);
//若未授权定位,则提示用户去设置
if (!isGrant) {
showFinishActivityDialog();
}
}
});
break;
case 3://打怪结果
String[] result = MessageData.split("/");
handleCatchResult(result[0],result[1],result[2]);
break;
}
}
2、Unity端调用android 方法
AndroidJavaClass jc = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject> ("currentActivity");
jo.Call ("makePauseUnity");
上面的三个参数,前两个是固定不变的,即"com.unity3d.player.UnityPlayer"和"currentActivity" 这两个参数是固定 不变的,之前,Unity开发那边,换成了我们app的包名和 Unity所在的Activity名称,发现怎么也调用不了。
上面就是在Android中嵌入Unity时,我遇到的一些坑,暂时只想到了这么多,后面想起来了再加上,同时如果有遇到同样问题的同学可以提问,力所能及的话就帮着解决。同时,如果有对我上面遇到的问题有更好的解决办法的,也请在下面留言,多谢多谢!
另外想学习Unity的同学可以去这位大神博客看看,挺详细的
废话:第一次在简书写博客,主要是太懒了。。。。明天就十一了,同事都下班了,国庆好好休息,这半个月累坏了,天天到家十二点、一点的,脑仁儿疼,下班走人。。。
更新:最后Unity退出还是用mUnityPlayer.quit()方法退出,不过Unity所在的Activity设置process属性,单独一个进程,这样退出时就会退出Unity所在的进程,而不会退出我们自己的app了