說一下需求,因?yàn)樽罱麬R有點(diǎn)火,大BOSS從手機(jī)淘寶發(fā)現(xiàn)了一個(gè)AR游戲,在“我的淘寶”右上角
有一個(gè)“抓喵喵”的游戲,好像是AR實(shí)現(xiàn)的(本人小白,不確定是不是AR。。。囧!),點(diǎn)進(jìn)去就是一個(gè)游戲,如下圖:
所以大BOSS發(fā)話了,也要弄一個(gè)出來,就集成到我們自己的App里,于是開干。。。。。。先介紹一下大體功能,點(diǎn)擊進(jìn)入游戲,先是一個(gè)加載頁,這個(gè)我沒上圖,就是一張介紹游戲規(guī)則的圖片加一個(gè)進(jìn)度條,說一下游戲場(chǎng)景里的功能,首先是一個(gè)地圖,看左下角。。。。。高德地圖 這里用到了地圖和定位功能,主角定位的位置就是我當(dāng)前位置,主角少女旁邊有很多怪物(喵喵),點(diǎn)擊喵喵,就進(jìn)入捕怪場(chǎng)景了(這些就屬于游戲部分了,具體功能大家可以下載或更新手機(jī)淘寶體驗(yàn)一下),抓捕到喵后就能獲取優(yōu)惠券折扣之類的。
本來的安排是游戲這塊是交給Unity開發(fā)做的,我們移動(dòng)端只需要在app上留個(gè)入口,然后加載Unity游戲就行,所以任務(wù)下來了后就了解了一個(gè)Android和Unity交互這塊的知識(shí)點(diǎn),這個(gè)網(wǎng)上太多了,我這里就不多說了,主要說一下,在嵌入U(xiǎn)nity過程中遇到的各種問題
Android 中嵌入U(xiǎn)nity
關(guān)于Android和Unity集成,網(wǎng)上有兩種方案,一種是把Android打包成jar,集成到Unity中,Unity中還要引入Android的SDK,另一種就是把Unity打包,然后解壓,把相關(guān)的jar包、資源文件引入Android項(xiàng)目中,我采用的是第二種,具體步驟我就不寫了(主要是懶。。。。),找了兩個(gè),大家可以看看 (交互步驟兩位作者都寫的十分清楚,贊!):
說一下我的問題,Unity開發(fā)大哥,寫了Demo,打包發(fā)給我,我照著網(wǎng)上的步驟先試著往項(xiàng)目里集成,一切都很OK,很輕松。然后問題來了,本來這個(gè)游戲場(chǎng)景是Unity做的,但是Unity那邊集成地圖出現(xiàn)了無法解決的問題,所以地圖這塊兒需要移動(dòng)端來做了,我有點(diǎn)忐忑,因?yàn)橐郧皬臎]集成過地圖,于是咬著牙硬上了,花了一天時(shí)間(囧!!!),把地圖弄出來了,剛開始用的是百度地圖,但是要求中心點(diǎn)不在地圖正中心(中心點(diǎn)不在地圖中心還叫中心點(diǎn)嗎?),弄半天沒弄出來,然后iOS哥們用的高德地圖,可以把中心點(diǎn)偏移出中心位置,于是建議我也用高德,好吧,換吧,正好統(tǒng)一下,換成高德地圖,OK,都弄好了,開始往里面集成Unity了,如下簡(jiǎn)單布局:
<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就是高德地圖,F(xiàn)rameLayout 就是Unity場(chǎng)景的父布局了,最后的那個(gè)ImageView就是最開始游戲加載的圖片。當(dāng)前的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游戲加載進(jìn)來
fl_unity_layout.addView(mUnityPlayer.getView());
//游戲加載圖片
iv_unity_loading = (ImageView) findViewById(R.id.iv_unity_loading);
//下面省略地圖初始化和定位設(shè)置。。。。
}
我按上面代碼跑了一下,發(fā)現(xiàn)Unity場(chǎng)景怎么也出不來,之前單獨(dú)加載Unity可是OK的,咋回事?而且我的Unity可是放在地圖上面的,后來查了一下,地圖的MapView和Unity都涉及到了OpenGL中的glsurfaceview,所以沖突了,想具體了解的可以看看OpenGL和glSurfaceView,于是我把這里的MapView換成了TextureMapView,解決了Unity不顯示的問題
Unity背景不透明問題
解決了Unity不顯示問題后,出現(xiàn)了另一個(gè)問題,那就是Unity場(chǎng)景的背景色是一片漆黑色的,即便Unity那邊設(shè)置了背景透明,打包后集成到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
硬著頭皮看了幾篇英文文章,都遇到了這個(gè)問題,發(fā)現(xiàn)只有Unity開發(fā)版本降到4.2才行,這叫一個(gè)坑!!!沒辦法,現(xiàn)在Unity開發(fā)大哥的Unity開發(fā)版本是2017的,最后把版本降到了4.9,沒解決,再降到4.2,搞到了半夜,還是沒弄出來,回家。。。。。第二天早上,稍改了一下,試跑了一下,居然可以了,當(dāng)時(shí)激動(dòng)的,這個(gè)問題搞了兩天,終于好了。。。。解決辦法是參照上面第三個(gè)鏈接改好的,但是只能Unity 4.2版本打的包有效果,其他版本都不行,操蛋!!!
Unity-class.jar 混淆問題
解決了背景透明問題,心情大好,想看看集成了Unity游戲后apk有多大,于是準(zhǔn)備打個(gè)release包看看,但是,一打release包就報(bào)錯(cuò),但是debug包沒問題啊,也能直接在手機(jī)上運(yùn)行,錯(cuò)誤如下:
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))
在網(wǎng)上查了一下,好像是混淆的時(shí)候出的問題,在build.gradle 文件里一看,debug版沒有進(jìn)行混淆,所以沒有問題,但是release混淆就出問題了,所以,很明顯,就是untiy-class.jar 這個(gè)包混淆時(shí)出問題了,于是照著網(wǎng)上的方案,將proguard-rules.pro 文件里的混淆規(guī)則改了一下
-dontwarn com.unity3d.player.**
-dontwarn org.fmod.**
-dontwarn bitter.jnibridge.**
-keep class com.unity3d.player.** {*;}
-keep class org.fmod.** {*;}
-keep class bitter.jnibridge.** {*;}
再打包。。。。還是報(bào)錯(cuò),還是這個(gè)問題,摔桌。。。。。
再搜。。。。
找到了遇到這個(gè)問題的解決方案:
android引入unity-classes.jar之后進(jìn)行混淆的問題解決
作者遇到的問題和我的簡(jiǎn)直一毛一樣啊,興奮。。。。。照著作者的方案,把ProGuard 的源文件改了,使用ant 重新編譯了,然后再打包。。。。。還是報(bào)一樣的錯(cuò),再摔桌。。。。。
再發(fā)幾個(gè)相同解決方案的鏈接:
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/
然而,都沒解決我的問題,有點(diǎn)小絕望了。。。。
最后仔細(xì)想想,應(yīng)該是版本的問題,幾位作者的帖子大都是三四年前的了,可能版本更新后,有的問題就不能照原辦法解決了,問題卡這兒了。。。。
這個(gè)打包的問題還沒解決,另一個(gè)問題又來了,4.2版本的Unity打包的程度在app上陀螺儀失效了,在這個(gè)游戲里,用戶是可以拿著手機(jī)移動(dòng)方位的,效果就是地圖也會(huì)隨著屏幕旋轉(zhuǎn),而且游戲中的人物也會(huì)隨著屏幕旋轉(zhuǎn)的,比如,上圖中,有一只喵在少女的后面,沒顯示出來,需要用戶拿著手機(jī)轉(zhuǎn)個(gè)身,將身后的怪物顯示在屏幕當(dāng)中,這里的Unity中有陀螺儀效果,喵喵會(huì)隨著屏幕旋轉(zhuǎn)而出現(xiàn)/隱藏于屏幕中,在iOS中是可以的,但是到了Android里就不行了,地圖可以旋轉(zhuǎn),但是Unity中的模型動(dòng)不了。。。。。
android端開發(fā)頓時(shí)卡住了,而Unity開發(fā)大哥因?yàn)閍ndroid這方面的問題就先顧iOS去了,先把iOS搞出來,再來解決android問題
替換方案
android這邊兩個(gè)問題最終還是沒解決,一個(gè)是4.2版本的打包問題(2017版Unity的可以打包),另一個(gè)就是陀螺儀失效問題。。。。
最后技術(shù)老大拍板拿了方案,地圖和怪物顯示都android來做,Unity只負(fù)責(zé)打怪場(chǎng)景(Unity開發(fā)版本用2017版),當(dāng)然,只是android端。。。苦逼
只能在地圖上動(dòng)手腳了,要在地圖上顯示怪物,只能看自定義 Marker,我是拿到當(dāng)前位置的經(jīng)緯度,然后通過隨機(jī)數(shù),在當(dāng)前位置上隨機(jī)增加或減少經(jīng)度和緯度,將怪物分布到當(dāng)前位置的附近,再設(shè)置MarkerOption來顯示怪物的圖片
//隨機(jī)生成經(jīng)緯度
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);
當(dāng)然這個(gè)效果和iOS端用Unity實(shí)現(xiàn)的效果比起來就差多了,但是我暫時(shí)沒其他解決辦法了。。。。一個(gè)是怪物不能動(dòng),不像Unity實(shí)現(xiàn)的那樣能夠動(dòng)而且是3D效果的,地圖上只有一張圖片,第二個(gè)就是圖片還是固定大小的,不能設(shè)置,想把怪物顯示大一點(diǎn)都不行,若是哪位同學(xué)對(duì)高德地圖API里設(shè)置marker這塊比較熟悉的話望告知,謝謝
Unity退出問題
這里還有個(gè)坑爹問題,就是Unity退出的時(shí)候,會(huì)將整個(gè)進(jìn)程殺掉,導(dǎo)致app重啟。。。。
Unity中退出使用的是 mUnityPlayer.quit() 方法,但是我們看看這個(gè)方法的代碼:
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();
}
//這里是關(guān)鍵
this.kill();
g();
}
關(guān)鍵代碼是在倒數(shù)第三行 this.kill(); 下面是kill()方法:
protected void kill() {
Process.killProcess(Process.myPid());
}
殺死進(jìn)程啊,有木有。。。。。所以APP就重啟了啊。。。。
網(wǎng)上千篇一律的都說加個(gè)按鈕調(diào)用mUnityPlayer.quity() 方法,或是在onBackPress方法中調(diào)用mUnityPlayer.quity() 方法,這樣和直接調(diào)用 mUnityPlayer.quity()有什么不一樣么,都重啟app了,或許我用的方法不對(duì)?
稍靠譜點(diǎn)的就是android端調(diào)用Unity方法,Unity端執(zhí)行退出,另一個(gè)方案就是將Unity單獨(dú)放另一個(gè)進(jìn)程里,這樣退出的話就不會(huì)殺死app所在的進(jìn)程了。
但是這兩種方案我都試過了,第一種,Unity端退出執(zhí)行的還是quity() 方法,最后還是把進(jìn)程給殺了,第二種試了也沒效果。。。
最后想了個(gè)笨方法,就是用戶點(diǎn)返回按鈕或是退出這個(gè)繼承 UnityPlayerActivity的Activity時(shí),不將這個(gè)Activity 關(guān)閉,而是將這個(gè)Activity的啟動(dòng)模式設(shè)置為singleInstance,即每次打開的時(shí)候?qū)⑵鋯为?dú)放在一個(gè)任務(wù)棧里(因?yàn)閍pp主頁是singleTask模式,進(jìn)入主頁時(shí),會(huì)將其上面的activity都清除出棧,為了避免UnityPlayerActivity子類被清除,所以將其設(shè)置為singleInstance,作為一個(gè)單例),這樣這個(gè)包含Unity游戲的Activity就不會(huì)finish掉,同時(shí)也解決了每次加載Unity游戲時(shí)時(shí)間過長(zhǎng)的問題,就第一次加載時(shí)花費(fèi)一些時(shí)間,下次就不用再花時(shí)間來加載了。。。。。我們Unity加載時(shí)間得幾十秒鐘,得等十一過后,Unity開發(fā)大哥來解決這個(gè)問題了。
其他要注意的問題
1、Unity調(diào)用Android是在子線程中運(yùn)行的,所以如果涉及到UI操作,記得切換到主線程
/**
* 供Unity端調(diào)用
* 接收Unity傳遞過來的數(shù)據(jù)
*
* @param MessageData
*/
public void getUnityMessage(int messageType, String MessageData) {
switch (messageType) {
case 2://獲取怪物列表
runOnUiThread(new Runnable() {
@Override
public void run() {
//隱藏加載中圖片和Unity場(chǎng)景,顯示地圖
iv_unity_loading.setVisibility(View.GONE);
fl_unity_layout.setVisibility(View.GONE);
//若未授權(quán)定位,則提示用戶去設(shè)置
if (!isGrant) {
showFinishActivityDialog();
}
}
});
break;
case 3://打怪結(jié)果
String[] result = MessageData.split("/");
handleCatchResult(result[0],result[1],result[2]);
break;
}
}
2、Unity端調(diào)用android 方法
AndroidJavaClass jc = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject> ("currentActivity");
jo.Call ("makePauseUnity");
上面的三個(gè)參數(shù),前兩個(gè)是固定不變的,即"com.unity3d.player.UnityPlayer"和"currentActivity" 這兩個(gè)參數(shù)是固定 不變的,之前,Unity開發(fā)那邊,換成了我們app的包名和 Unity所在的Activity名稱,發(fā)現(xiàn)怎么也調(diào)用不了。
上面就是在Android中嵌入U(xiǎn)nity時(shí),我遇到的一些坑,暫時(shí)只想到了這么多,后面想起來了再加上,同時(shí)如果有遇到同樣問題的同學(xué)可以提問,力所能及的話就幫著解決。同時(shí),如果有對(duì)我上面遇到的問題有更好的解決辦法的,也請(qǐng)?jiān)谙旅媪粞裕嘀x多謝!
另外想學(xué)習(xí)Unity的同學(xué)可以去這位大神博客看看,挺詳細(xì)的
廢話:第一次在簡(jiǎn)書寫博客,主要是太懶了。。。。明天就十一了,同事都下班了,國(guó)慶好好休息,這半個(gè)月累壞了,天天到家十二點(diǎn)、一點(diǎn)的,腦仁兒疼,下班走人。。。
更新:最后Unity退出還是用mUnityPlayer.quit()方法退出,不過Unity所在的Activity設(shè)置process屬性,單獨(dú)一個(gè)進(jìn)程,這樣退出時(shí)就會(huì)退出Unity所在的進(jìn)程,而不會(huì)退出我們自己的app了