在原生APP中整合Unity容器

2023-08-22 06:01:27
隨著技術的發展,越來越多的APP期望擁有3D,AR的能力。要達到這個目標可以選擇使用原生開發,也可以使用Unity成熟的3D開發技術鏈,通過嵌入的方式將Unity容器嵌入到APP中。這裡介紹的是通過嵌入Unity容器的方式來實現APP的3D,AR能力的。
 
Unity整合到iOS應用的本質是將Unity中所有用到的資源,模型,程式碼打包成一個framework動態庫,然後嵌入到APP的內部。APP啟動後載入Unity頁面時,會載入這個framework動態庫和裡面的所有資源。然後在APP中展示這個Unity頁面。
 
Unity整合到iOS應用的官方提供的方案是以Workspace的方式整合,具體步驟如下:
1.Unity專案通過Unity Editer 匯出一個iOS專案,這個iOS專案裡包含了整個Unity專案的所有程式碼和資源,可以直接執行在iOS系統上。
2.用Xcode建立一個原生的iOS專案
3.用Xcode建立一個Workspace, 然後通過add file 的形式將這2個專案新增到一個Workspace下面。
4.修改Unity-iPhone專案打包設定,使其打包出適合嵌入的UnityFramework動態庫,
5.修改NativeiOSApp專案設定,新增UnityFramework動態庫。

 

對Workspace下面的2個專案進行設定
Unity-iPhone建立
在生成UnityFramework動態庫前,需要將Unity專案匯出一個iOS專案,匯出的過程如下:
1.點選File->Building Settings進入到選擇匯出頁面

2.選擇要匯出的平臺,這裡選擇iOS,然後點選右下角的"Switch Plateform"按鈕,然後點選上面的「Add Open Scenes」新增場景

3.點選「Build」,匯出iOS專案

 

Unity-iPhone設定
關閉以Workspace方式開啟的Xcode,單獨開啟Unity-iPhone專案
1.在Unity-iPhone專案中,設定Data 這個Group的 Target Memebership, 將裡面的UnitFramework項打勾,表示這個Data在生成Framework靜態庫時也要作為其中的一員,放置進去。
2.將NativeCallProxy.h標頭檔案從Framework的 protect組移動到public組,將標頭檔案開放出來。路徑為: Unity-iPhone / Libraries / Plugins / iOS / NativeCallProxy.h
3.手機連線電腦,執行,生成靜態庫。

 

NativeiOSApp原生專案設定
以Workspace方式開啟專案
1.在NativeiOSApp Target - General - Frameworks, Libraries, and Embedded Content 通過點選+ 將UnityFramework設定嵌入到專案中
2.在NativeiOSApp Target - Build Phases - Link Binary With Libraries中,點選-,將UnityFramework移除,表示禁止在專案連結時將UnityFramework連結到可執行檔案中,它只作為內嵌在APP中的靜態庫在執行時載入使用。
3.手機連線電腦,執行,看到Demo執行的效果。
 
 
Unity與原生通訊
Unity呼叫原生
1.首先在原生中實現代理協定NativeCallProxy中的方法(註冊由誰來處理unity的呼叫)和新增暴露給unity的方法。
NativeCallProxy.h檔案中方法的宣告
__attribute__ ((visibility("default")))
@interface FrameworkLibAPI : NSObject
// call it any time after UnityFrameworkLoad to set object implementing NativeCallsProtocol methods
+(void) registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi;

@end
NativeCallProxy.cpp檔案中方法的實現
@implementation FrameworkLibAPI

id<NativeCallsProtocol> api = NULL;
+(void) registerAPIforNativeCalls:(id<NativeCallsProtocol>) aApi
{
    api = aApi;
}

@end
給Unity暴露的原生呼叫方法
extern "C" {
    void showHostMainWindow(const char* color) { return [api showHostMainWindow:[NSString stringWithUTF8String:color]]; }
}
2.然後在原生的Unity啟動時,註冊這個處理的物件
- (void)initUnity
{
    [self setUfw: UnityFrameworkLoad()];
    // Set UnityFramework target for Unity-iPhone/Data folder to make Data part of a UnityFramework.framework and uncomment call to setDataBundleId
    // ODR is not supported in this case, ( if you need embedded and ODR you need to copy data )
    [[self ufw] setDataBundleId: "com.unity3d.framework"];
    [[self ufw] registerFrameworkListener: self];
    [NSClassFromString(@"FrameworkLibAPI") registerAPIforNativeCalls:self];
 }
3.在Unity中,呼叫iOS的原生方法
Cube.cs指令碼中宣告在APP中存在方法showHostMainWindow
#if UNITY_IOS || UNITY_TVOS
public class NativeAPI {
    [DllImport("__Internal")]
    public static extern void showHostMainWindow(string lastStringColor);
}
#endif
Cube.cs指令碼中新增Unity 2d按鈕點選事件,呼叫iOS原生方法
void OnGUI()
{
    GUIStyle style = new GUIStyle("button");
    style.fontSize = 45;
    if (GUI.Button(new Rect(10, 300, 600, 100), "Show Main With Color", style)) showHostMainWindow();
}


void showHostMainWindow()
{
#if UNITY_ANDROID
    try
    {
        AndroidJavaClass jc = new AndroidJavaClass("com.unity.mynativeapp.SharedClass");
        jc.CallStatic("showMainActivity", lastStringColor);
    } catch(Exception e)
    {
        appendToText("Exception during showHostMainWindow");
        appendToText(e.Message);
    }
#elif UNITY_IOS || UNITY_TVOS
    NativeAPI.showHostMainWindow(lastStringColor);
#endif
}

 

原生呼叫Unity
點選原生傳送訊息按鈕,發訊息給Unity。
self.btnSendMsg = [UIButton buttonWithType: UIButtonTypeSystem];
[self.btnSendMsg setTitle: @"Send Msg" forState: UIControlStateNormal];
[self.btnSendMsg addTarget: self action: @selector(sendMsgToUnity) forControlEvents: UIControlEventPrimaryActionTriggered];


- (void)sendMsgToUnity
{
    [[self ufw] sendMessageToGOWithName: "Cube" functionName: "ChangeColor" message: "yellow"];
}

/*
goName: 場景中的遊戲物體GameObject
name: 這個遊戲物體掛載的指令碼中的一個方法
msg: 引數
*/
- (void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg
{
    UnitySendMessage(goName, name, msg);
}

void  UnitySendMessage(const char* obj, const char* method, const char* msg);
Cube.cs指令碼中的方法實現
string lastStringColor = "";
void ChangeColor(string newColor)
{
    appendToText( "Changing Color to " + newColor );

    lastStringColor = newColor;

    if (newColor == "red") GetComponent<Renderer>().material.color = Color.red;
    else if (newColor == "blue") GetComponent<Renderer>().material.color = Color.blue;
    else if (newColor == "yellow") GetComponent<Renderer>().material.color = Color.yellow;
    else GetComponent<Renderer>().material.color = Color.black;
}
 
Unity熱更新
在Unity專案中使用純C#構建的漸漸不能適應行動端的需求,行動端通常通過新增熱更新外掛來實現熱更新功能。這個過程起關鍵作用的是Lua和起對應的直譯器。
lua是直譯語言,並不需要事先編譯成塊,而是執行時動態解釋執行的。
這樣LUA就和普通的遊戲資源如圖片,文字沒有區別,因此可以在執行時直接從WEB伺服器上下載到持久化目錄並被其它LUA檔案呼叫。
Lua熱更新解決方案是通過一個Lua熱更新外掛(如ulua、slua、tolua、xlua等)來提供一個Lua的執行環境以及和C#進行互動。
所以lua熱更新的流程可以簡單理解為:將邏輯程式碼使用指令碼實現,再將指令碼轉化為文字資源,最後以更新資源的形式來更新程式,以此來實現熱更新。
注意:
在iOS應用內的Unity容器中,Untiy的C#資源和Lua資源都是在Unity容器內部進行載入的。如果Unity採用的Lua熱更新,則需要原生側在Lua指令碼和資源下載完成後通知Unity進行載入展示。


Demo地址
NativeiOSApp專案地址:https://github.com/zhfei/uaal-example
將 Unity 整合到原生 iOS 應用程式中官方檔案:https://docs.unity3d.com/cn/2020.2/Manual/UnityasaLibrary-iOS.html