Android中嵌入Unity游戏

作者: 关山明月 | 来源:发表于2017-09-30 20:34 被阅读426次

    说一下需求,因为最近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项目中,我采用的是第二种,具体步骤我就不写了(主要是懒。。。。),找了两个,大家可以看看 (交互步骤两位作者都写的十分清楚,赞!):

    初识untiy3d与安卓交互 作者:暗尘随码去

    Android与Unity交互研究

    说一下我的问题,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的同学可以去这位大神博客看看,挺详细的

    雨松MOMO---Unity

    废话:第一次在简书写博客,主要是太懒了。。。。明天就十一了,同事都下班了,国庆好好休息,这半个月累坏了,天天到家十二点、一点的,脑仁儿疼,下班走人。。。

    更新:最后Unity退出还是用mUnityPlayer.quit()方法退出,不过Unity所在的Activity设置process属性,单独一个进程,这样退出时就会退出Unity所在的进程,而不会退出我们自己的app了

    相关文章

      网友评论

      • normidar:坑坑之路~~~~~~
      • 辣条超人:启动新进程过慢的问题可以这么弄:弹出个progressDialog,然后启动一个新进程中的Service,Service启动成功以后新进程就启动起来了。然后让刚刚启动起来的Service发送一个自定义广播,这个广播是可以跨进程的。主线程收到广播以后关闭progressDialog,然后启动运行在新线程中的UnityPlayerActivity。这样UnityPlayerActivity的启动时间就大大减少,而且用户看到的progressDialog而不是白板体验要好很多。
        辣条超人:@辣条超人 我看你只有一篇文章我以为你已经不玩简书了。。
        辣条超人:@关山明月 :joy: 我好像把进程写成线程了
        关山明月:可行,我后来的做法就是进入页面时加载一个进度条布局,就是一张图片,底部一个进度条,进入页面成功后让这个布局隐藏起来
      • 辣条超人:让UnityPlayerActivity在新进程运行也是可以的。想要退出的时候直接调用finish()方法就行了。finish()之后会蹦就让他蹦,你主线程是不会蹦的。
        关山明月:对,我后来就是这样做的
      • 辣条超人:如果你在这个界面不会跳转到新界面的话可以考虑弄个UnityPlayer的子类,然后复写kill()方法。想要退出的时候调用finish()方法就行了。(如果在这个界面跳转新界面然后再finis()界面会崩溃,,我也不懂为啥)

      本文标题:Android中嵌入Unity游戏

      本文链接:https://www.haomeiwen.com/subject/bddnextx.html