iOS項目集成UnityFramework

背景:最近公司要做關(guān)于幼兒編程的項目,在iOS項目中需要使用視頻交互的功能,最后選擇的方案是使用Unity實現(xiàn),主要涉及到iOS原生項目和Unity界面來回切換,iOS和Unity的相互通信。

1、iOS原生項目和Unity界面來回切換

1.1 從iOS原生項目通過點擊按鈕進入Unity

- (void)enterUnityButtonClick {
  [self initUnity];
}

- (void)initUnity
{
    if([self unityIsInitialized]) {
        showAlert(@"Unity already initialized", @"Unload Unity first");
        return;
    }
    if([self didQuit]) {
        showAlert(@"Unity cannot be initialized after quit", @"Use unload instead");
        return;
    }
    
    [self setUfw: UnityFrameworkLoad()];
    [[self ufw] setDataBundleId: "com.unity3d.framework"];
    [[self ufw] registerFrameworkListener: self];
    [[self ufw] runEmbeddedWithArgc: gArgc argv: gArgv appLaunchOpts: appLaunchOpts];
    // set quit handler to change default behavior of exit app
    [[self ufw] appController].quitHandler = ^(){ NSLog(@"AppController.quitHandler called"); };
  }

UnityFramework* UnityFrameworkLoad()
{
    NSString* bundlePath = nil;
    bundlePath = [[NSBundle mainBundle] bundlePath];
    bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];
    
    NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
    if ([bundle isLoaded] == false) [bundle load];
    
    UnityFramework* ufw = [bundle.principalClass getInstance];
    if (![ufw appController])
    {
        // unity is not initialized
        [ufw setExecuteHeader: &_mh_execute_header];
    }
    return ufw;
}
//其中 [self ufw] 是所在類的一個熟悉
@property (nonatomic, strong) UnityFramework *ufw;
[[self ufw] runEmbeddedWithArgc: gArgc argv: gArgv appLaunchOpts: appLaunchOpts];
//gArgc 、 gArgv 這兩個參數(shù)是程序入口main函數(shù)中的參數(shù),可以使用NSUserDefaults保存起來
//appLaunchOpts 是- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 函數(shù)中的launchOptions參數(shù)

1.2 從Unity界面回到iOS界面

上面的函數(shù)正確調(diào)用后,從iOS界面跳轉(zhuǎn)到Unity應(yīng)該是沒問題了,那怎么從Unity退出回到iOS原生界面呢?

我們首先從Unity項目中導(dǎo)出Unity-iPhone項目中的接口文件UnityFramework看一下,接口文件如下圖
UnityFramework.png

很容易我們調(diào)用- (void)pause:(bool)pause函數(shù).

然后再調(diào)用- (void)unloadApplication或者- (void)quitApplication:(int)exitCode

結(jié)果LuaClient:Destroy(),連同iOS的程序也Crash了,那一定是調(diào)用的方法不對,或者順序不對?那到底問題出在哪了呢?

最終找到了一個iOS集成Unity項目的工程 https://github.com/Unity-Technologies/uaal-example ,最終才理清楚調(diào)用順序。

正確的退出順序:

1.2.1、遵守協(xié)議注冊監(jiān)聽

1、首先是所在的控制器要遵守協(xié)議 <UnityFrameworkListener>,監(jiān)聽Unity狀態(tài)的變化

2、 注冊監(jiān)聽 [[self ufw] registerFrameworkListener: self];

1.2.2、點擊退出按鈕
- (void)quitUnityButtonClick {
    if ([self unityIsInitialized]) {
        [UnityFrameworkLoad() unloadApplication];
    }
}

#pragma mark -UnityFrameworkListener
- (void)unityDidUnload:(NSNotification*)notification
{
    NSLog(@"unityDidUnload called");
    
    [[self ufw] unregisterFrameworkListener: self];
    [self setUfw: nil];
    [self.window makeKeyAndVisible];
}

2、創(chuàng)建一個能與iOS交互的Unity項目

2.1、引用接口文件遇到的鏈接錯誤

由于Unity-iPhone項目是另一個部門提供的,提供的接口文件在iOS項目中能編譯,但是鏈接時報錯,如下圖

UnityForiOSInteface.png
LinkerError.png

查閱了大量資料也沒搞清楚原因,最后在 Unity的官方文檔 中看到iOS去調(diào)用Unity有兩種方式

1、UnitySendMessage

2、delegates

由于我們的項目要在Unity啟動前就把數(shù)據(jù)準(zhǔn)備好,用于加載不同的內(nèi)容,UnitySendMessage這種方式只適用于Unity已經(jīng)啟動好了,iOS向Unity通信,所以就要使用delegates的方式進行通信

為了調(diào)試項目,只能自己創(chuàng)建一個Unity項目區(qū)驗證delegates這種方式是否行得通了,之前沒有Unity項目基礎(chǔ)就在騰訊課堂看到一個講解的還不錯的適合Unity入門的項目

2.2、創(chuàng)建一個Unity項目

https://ke.qq.com/course/3101943?taid=10573792874026231

3、iOS和Unity的相互調(diào)用:

UnityToiOS.png
iOS與Unity相互通信的文件放在Plugins/iOS/ 目錄下
//UnityForiOSInterface.h 文件

#import <Foundation/Foundation.h>

typedef void (*ResultHandler) (const char * _Nonnull object);

//Unity退出時觸發(fā)該通知
#define kQuitUnityNotification  @"kQuitUnityNotification"
//使用NSUserDefaults通過kCourseInfoKey保存課件信息
#define kCourseInfoKey          @"kCourseInfoKey"

NS_ASSUME_NONNULL_BEGIN

@interface UnityForiOSInterface : NSObject

#ifdef __cplusplus
extern "C" {
#endif
    
    void unitySendToiOSMessage(const char *msg); //打開Unity后,Unity回調(diào)課程的狀態(tài)給iOS
    void getiOSParams(ResultHandler resultHandler); //Unity調(diào)用iOS,iOS通過函數(shù)指針參數(shù)賦值的方式向Unity賦值
    
#ifdef __cplusplus
}
#endif
@end

NS_ASSUME_NONNULL_END

//UnityForiOSInterface.m 文件
#import "UnityForiOSInterface.h"

@implementation UnityForiOSInterface

void unitySendToiOSMessage(const char *msg) {
    NSLog(@"msg:%@",[NSString stringWithUTF8String:msg]);
    [[NSNotificationCenter defaultCenter] postNotificationName:kQuitUnityNotification object:[NSString stringWithUTF8String:msg]];
}

void getiOSParams(ResultHandler resultHandler) {
    NSString *paramsStr = [[NSUserDefaults standardUserDefaults] objectForKey:kCourseInfoKey];
    resultHandler (paramsStr.UTF8String);
}
@end
這里寫一個簡單的Unity項目 用于和iOS做交互,也可以參考騰訊課堂的Unity項目講解 ,簡單來說就是在場景上掛在一個腳本,通過點擊場景的按鈕由Unity向iOS發(fā)送消息
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using AOT;
using UnityEngine;
using UnityEngine.UI;

public class UnityCallObjc : MonoBehaviour
{
    public InputField input;  
    public Text text;

    [DllImport("__Internal")]
    static extern void UnitySendToiOSMessage(string msg);

    [DllImport("__Internal")]
    static extern void getiOSParams(IntPtr resultHandler);

    public void OnButtonClick()
    {
        //IOSLog(input.text);
        ResultHandler handler = new ResultHandler(resultHandler);
        IntPtr fp = Marshal.GetFunctionPointerForDelegate(handler);
        getiOSParams(fp);
    }

    //非托管方法
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ResultHandler(string resultString);

    [MonoPInvokeCallback(typeof(ResultHandler))]
    static void resultHandler(string resultStr)
    {
        Debug.LogFormat("result string = {0}", resultStr);
    }
}
Unity的主要語言是C#,因此可以利用C#的特性來訪問C語言所定義的接口,然后再通過C接口再調(diào)用ObjC的代碼,UnityCallObjc腳本為什么這么寫,Unity3D與iOS的交互,講得非常清楚了,具體可以看一下.

參考資料:

https://github.com/Unity-Technologies/uaal-example.git

https://ke.qq.com/course/3101943?taid=10573792874026231

https://docs.unity3d.com/Manual/PluginsForIOS.html

http://www.itdecent.cn/p/1ab65bee6692

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容