天天看點

Unity和Android混合開發

通用的流程

https://blog.csdn.net/zhangdi2017/article/details/65629589

應用場景

Unity遊戲中一些功能需要安卓系統的支援,如搜尋wifi等。而且想接入SDK時,很多都是針對安卓的SDK,很少有針對Unity的,是以必須要學習Unity和Android的互調。

網上能搜到很多相關的内容,但大多由于年代久遠,Unity和Android Studio版本更新等問題,導緻各種無盡的報錯讓人崩潰,是以還是要記錄一下。

環境

  • Unity5.6或2017.3.0f3 + JDK1.8.0_131 + Android Studio3.0.1 + gradle-4.1-all + Visual Studio2017
  • Unity2017高版本取消了Internal的打包方式。
  • VS還好,另外幾個要小心各種版本感人莫名其妙的Bug相容問題!!!!!
  • JDK可以是7或8,一定不能是9!
  • Android Studio上次手賤點了更新,升到v3.1後打包出來目錄結構有變動,找不到jar包的位置。且打出的aar包在Unity中調用顯示找不到目标方法。
Unity和Android混合開發
Unity和Android混合開發

Unity與Android的互動有兩種思路

http://www.sikiedu.com/course/137/task/4910/show#

一、Unity做好項目之後導出為Android Studio項目,導入到Android Studio中進行之後的功能開發。最後由Android Studio打包APK。即Unity輔助Android開發(Android開發為主),對Android技能要求較高。

二、Android Sutido做好項目導出jar或aar包,導入到Unity中作為Unity的插件使用,最後由Unity打包APK。即Android輔助Unity開發(Unity開發為主),對Unity技能要求較高。

Unity打包APK時,調用安卓SDK,把所有遊戲内容整合打包出的APK中隻有一個MainActivity。

一、導出Jar包 + 擴充MainActivity + Java主導

Unity調安卓

複雜度 4.5★    通用度 4.5★    注:官方已經不再推薦這種方法。

http://www.sikiedu.com/course/137/task/4911/show#

1、打開Android Studio建立一個項目,建立一個子產品(Module),取名UnityAndroidLibrary。注意選擇最小SDK16,因為Unity最小支援的是16。

2、在該子產品(ProjectName/UnityAndroidLibrary/src/main/java/packageName/)下建立一個Empty Activity。建立時勾上Launcher Activity。

3、删除跟該界面一同生成的activity_main.xml布局檔案(因為之後布局歸Unity管理),同時删除該子產品MainActivity中onCreate()裡調用setContentView()方法。

4、進入Unity的安裝目錄(如D:\Unity 5.4.3f1\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes下)複制classes.jar檔案,粘貼到該子產品UnityAndroidLibrary/libs目錄下。右鍵該jar選擇Add as Library,選Add to Module UnityAndroidLibrary。

5、打開UnityAndroidLibrary子產品的AndroidManifest.xml清單檔案,該檔案會覆寫掉Unity的一些設定,修改如下。(從預設的app子產品中的清單檔案拷貝過來,把報錯的地方去掉即可。記得加後面的meta-data節點)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="guxin.demo.unityandroidlibrary">

    <application
        android:allowBackup="true"
        android:label="UnityAndroidTest"
        android:supportsRtl="true">
        <activity android:name=".MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
                <meta-data android:name="unityplayer.UnityActivity" android:value="true"/>
            </intent-filter>
        </activity>
    </application>

</manifest>      

6、回到子產品的MainActivity,修改該類繼承自UnityPlayerActivity。在該類中添加自定義的方法,用于給Unity調用。如:

public int add(int a, int b){
    return a + b;
}      

7、在AS中Project目錄選中unityandroidlibrary,在Build菜單下選Make Module ‘unityandroidlibrary’單獨編譯這個子產品。

8、在unityandroidlibrary/build/intermediates/bundles/debug目錄右鍵Show in Explorer。删除debug/libs/classes.jar(等同于剛從Unity那邊拷過來的内容),把debug/classes.jar拖到debug/libs中(這個是包含了剛新增的擴充方法的)。把libs和res這兩個檔案夾備份(如複制到桌面)。 

Unity和Android混合開發

 

9、在unityandroidlibrary/build/intermediates/manifests/full/debug/AndroidManifest.xml右鍵Show in Explorer,也把這個清單檔案複制出來(如複制到桌面)。打開在桌面的副本,修改package包名為在Unity中想要的包名,如包名最後一段改為unityandroidtest(注意包名要全部小寫)。

Unity和Android混合開發

10、打開Unity,建立一個工程UnityAndroidTest,Build Settings切換為安卓平台,Player Settings中修改包名,包名同上一步的一緻(先調平台再調包名)。在Assets下建立檔案夾Plugins/Android(名字固定的,小心别漏了s),将上兩步得到的三個檔案拖到該檔案夾中。

Unity和Android混合開發

11、建立一個C#腳本,VS打開編輯如下。把該腳本挂到任一場景中的遊戲對象上(如Main Camera)。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    public Text text; // 用于展示調用結果

    void Start ()
    {
        // 獲得位于com.unity3d.player包下的UnityPlayer類,固定寫法。
        AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");   // 參數是包名+類名
        // 獲得jc所代表的類裡的currentActivity對象,固定寫法。這是Unity提供的classes.jar中的功能,可通過currentActivity擷取到安卓端代表MainActivty的對象。
        AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
        // 調用MainActivty中的自定義方法。
        text.text = jo.Call<int>("add", 1, 2).ToString();
    }

}      

12、最後一步,連上真機并打開USB調試,Build & Run,路徑選擇桌面。此時會把APK輸出到桌面并安裝到真機上運作,即可看到調用結果。下圖是我用AS的安卓模拟器(AVD)運作的效果。注意了,要打包釋出出來用,直接在Unity編輯器點運作會報錯,但打包出來安卓機運作沒有問題的。

Unity和Android混合開發

安卓調Unity

繼續上面的工程。

1、在Unity剛才的腳本中,添加一個方法,給安卓調用。

// 交由安卓調Unity。
public void ChangeColor()
{
    text.color = Color.red;
}      

2、在安卓端unityandroidlibrary子產品的MainActivity類中,修改add()方法。該方法由Unity調用,方法中再由安卓調Unity中的方法,進而實作兩者的互調。

// Unity調安卓
public int add(int a, int b){
    // 安卓調Unity
    UnityPlayer.UnitySendMessage("Main Camera", "ChangeColor", ""); // 參數:GameObject名 + 方法名 + 參數
    return a + b;
}      

3、重新把該子產品編譯。build菜單下選Make Module 'unityandroidlibrary'。同樣是在unityandroidlibrary/build/intermediates/bundles/debug目錄右鍵Show in Explorer,拖拽debug/classes.jar到libs中替換掉libs/classes.jar。把libs和res檔案夾拷貝到桌面。然後放到Unity中替換掉之前Assets/Plugins/Android目錄下的libs和res檔案夾。(AndroidManifest.xml可以不用替換)

4、Build & Run 。效果如下。可以看到Text文本顔色變紅色了。

Unity和Android混合開發

關于APP在手機桌面上顯示的軟體名稱

當Assets/Plugins/Android/AndroidManifest.xml中的android:label節點指定的軟體名,與Unity的Player Settings中Product Name指定的軟體名不一緻時,最終打出的APK包以前者為準!如下圖。

Unity和Android混合開發
Unity和Android混合開發

如果上面兩者不一緻,以安卓清單中的軟體名為準。

二、導出aar包 + 擴充MainActivity + Java主導

複雜度 4★    通用度 4.5★    注:官方推薦

http://www.sikiedu.com/course/137/task/4915/show#

1、接着上面的項目繼續。跟前面導出Jar包的差別是,導出Jar包時(已放到Unity中Assets/Plugins/Android/libs/classes.jar),res檔案夾不能跟jar打包到一起,是以Unity2017推薦我們導出aar包(其實解壓後就是jar + res)。

2、為示範aar包中包含了資源,在unityandroidlibrary子產品的清單檔案中加上icon(從另一個app子產品的複制),把相應的res/mipmap也複制過來。Build菜單選擇‘Build Module unityandroidlibrary’。

3、編譯完成後,這次不需要再像上面導出jar那樣去找res和libs檔案夾,直接找到unityandroidlibrary/build/outputs/aar檔案夾,裡面就是導出的aar包。把這個aar包和清單檔案(還是在unityandroidlibrary/build/intermediates/manifests/full/debug/AndroidManifest.xml)一并拷貝到桌面。

4、打開桌面的清單檔案,修改為想要的包名。例如最後一段改為unityandroidtest(全小寫,跟Unity中的保持一緻)。

5、打開桌面的aar包(zip壓縮工具可檢視),注意,把包裡根目錄的classes.jar移動到libs裡覆寫裡面較大的classes.jar。(複制粘貼,然後删掉libs外面的)

Unity和Android混合開發

6、觀察aar包内容可知,相比導出jar包的方式(隻有libs、res、清單xml)多了aidl、jni(提供安卓的進階功能)和R檔案、assets(資源檔案),這也是Unity推薦使用導出aar替代導出jar的原因之一。

7、把桌面的清單xml檔案剪切到Unity的Assets/Plugins/Android/裡,這份xml是指導Unity打包APK的總清單,它的包名需要與Unity裡寫的一緻,相對aar包來說是外部的清單。

8、打開桌面aar包裡的xml清單檔案,删除android:icon和android:label這兩行并儲存。因為這個xml清單是該子產品自己的,為了防止跟aar包外的總清單沖突。

9、最後,再把改好的aar包導入Unity的Assets/Plugins/Android中,項目結構如下。可以看到Unity把aar包識别成了一個插件。

Unity和Android混合開發

10、啟動安卓模拟器,Unity執行Build & Run後即可看到效果。與導出jar包的運作結果一緻。

注意:可能會出現Unity能導出APK,但是不能安裝到模拟器上的,Unity報錯如下。

Unity和Android混合開發

 直接把APK拖到模拟器中安裝,模拟器報錯如下。

Unity和Android混合開發

原因是模拟器上面有過該軟體的早期版本,要先解除安裝了才能再裝新版本上去。

關于I/Unity: AndroidJavaException: java.lang.NoSuchMethodError: no non-static method with name='add' signature='(II)I' in class Ljava.lang.Object;

03-30 02:08:29.073 3957-3972/guxin.demo.unityandroidtest I/Unity: AndroidJavaException: java.lang.NoSuchMethodError: no non-static method with name='add' signature='(II)I' in class Ljava.lang.Object;
    java.lang.NoSuchMethodError: no non-static method with name='add' signature='(II)I' in class Ljava.lang.Object;
        at com.unity3d.player.ReflectionHelper.getMethodID(Unknown Source)
        at com.unity3d.player.UnityPlayer.nativeRender(Native Method)
        at com.unity3d.player.UnityPlayer.c(Unknown Source)
        at com.unity3d.player.UnityPlayer$c$1.handleMessage(Unknown Source)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:154)
        at com.unity3d.player.UnityPlayer$c.run(Unknown Source)
      at UnityEngine.AndroidJNISafe.CheckException () [0x00000] in <filename unknown>:0 
      at UnityEngine.AndroidJNISafe.CallStaticObjectMethod (IntPtr clazz, IntPtr methodID, UnityEngine.jvalue[] args) [0x00000] in <filename unknown>:0 
      at UnityEngine.AndroidReflection.GetMethodMember (IntPtr jclass, System.String methodName, System.String signature, Boolean isStati      

更新到Android Studio3.1後會出現這個問題,打包出來aar,在Unity中調用顯示找不到add()方法!重裝Android Studio3.0.1版本。

如果還有該報錯,重新建立一個工程,或改用Internal來取代Gradle打包。(高版本Unity2017将取消Internal打包方式)

http://www.sikiedu.com/course/137/thread/1372

三、互調模式之提供Java擴充類

提供額外的類 + Java主導       複雜度 3★    通用度 3.5★

适用場景:在安卓中用Java執行一些操作,且不需要資源(圖檔等)的情況。可以把Java代碼封裝成jar後導入Unity中供Unity調用。由于在安卓端編寫代碼,可以使用安卓的各種進階文法(監聽器等)。但由于Test類不能用安卓上下文Context對象,能力有限,是以通用性不高(想傳入安卓上下文對象需要額外的操作)。跟上一種方法相比,不是擴充MainActivity,而是另寫到一個類中(也是以失去了上下文)。

1、打開AS建立一個工程叫SimpleClass,建立一個Module(選Android Library)取名SimpleLibrary。把Unity的classes.jar包導入到該子產品中(跟上面一樣,這是讓安卓能調用Unity的标準操作,即若隻想Unity調安卓可以不導入該classes.jar)。

2、建立一個Test類。在該擴充類中編寫跟Unity互動的方法。

Unity和Android混合開發
public class Test {
    public int add(int a, int b){
        UnityPlayer.UnitySendMessage("Main Camera", "ChangeColor", "");
        return a + b;
    }
}      

3、Build該子產品後,切到Project視圖,把simplelibrary/build/intermediates/bundles/debug/classes.jar檔案拖到Unity中Asset/Plugins/Android目錄下。

Unity和Android混合開發

4、複制到Unity後,可以給改jar包改名,如改為test.jar。

Unity和Android混合開發

5、Unity中調用該方法。非靜态方法由類的對象來調用。

void Start ()
{
    // 獲得安卓中test.jar中的Test類對象
    AndroidJavaObject jo = new AndroidJavaObject("me.guxin.simplelibrary.Test"); // 參數:包名,調用空參構造函數
    // 調用類中的自定義方法。
    text.text = jo.Call<int>("add", 1, 2).ToString();

    /* 如果的靜态方法
    // 獲得安卓中test.jar中的Test類
    AndroidJavaClass jc = new AndroidJavaClass("me.guxin.simplelibrary.Test");
    // 調用靜态方法
    text.text = jc.CallStatic<int>("add", 1, 2).ToString();
    */
}      

6、打包APK後丢到安卓模拟器中運作,即可看到運算結果。

四、互調模式之C#主導式調用

特點:Unity中設定了安卓SDK路徑後,可以直接調用安卓SDK中的原生方法,不用從AS中導出插件再導入Unity中。但C#中用反射的寫法不能使用Java的一些進階文法。

對于不熟悉安卓開發的同學,可以打開Android Studio建立一個空工程,檢視安卓Log()方法的簽名,再在Unity中照着安卓的結構寫C#代碼調用安卓SDK原生方法。

手動列印的安卓錯誤日志,可以在AS的Log Cat中看到輸出。

void Start ()
{
    AndroidJavaClass jc = new AndroidJavaClass("android.util.Log"); // 安卓SDK中的包名
    text.text = jc.CallStatic<int>("e", "UnityAndroidTest", "ErrorTest").ToString(); // 相當于在安卓中寫 Log.e("UnityAndroidTest", "ErrorTest");
}      

五、各種模式的适用情況

Unity和Android混合開發

(圖來源 http://www.sikiedu.com/course/137/task/4918/show)

Unity中接安卓SDK通常使用的組合:導出aar + 拓展MainActivity + Java主導 。因為第三方SDK通常是争對安卓平台做的。

簡單功能可用組合: Class拓展 + C#主導 。适用于簡單功能,如調用安卓土司Toast。好處是不用在AS導出項目給Unity當插件使用。

參考:

  • http://www.sikiedu.com/course/137
  • https://www.jianshu.com/p/c06063a403c6