背景:最近公司要做關于幼兒編程的項目,在iOS項目中需要使用視頻交互的功能,最后選擇的方案是使用Unity實現,主要涉及到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 這兩個參數是程序入口main函數中的參數,可以使用NSUserDefaults保存起來
//appLaunchOpts 是- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 函數中的launchOptions參數
1.2 從Unity界面回到iOS界面
上面的函數正確調用后,從iOS界面跳轉到Unity應該是沒問題了,那怎么從Unity退出回到iOS原生界面呢?
我們首先從Unity項目中導出Unity-iPhone項目中的接口文件UnityFramework看一下,接口文件如下圖
很容易我們調用- (void)pause:(bool)pause函數.
然后再調用- (void)unloadApplication或者- (void)quitApplication:(int)exitCode
結果LuaClient:Destroy(),連同iOS的程序也Crash了,那一定是調用的方法不對,或者順序不對?那到底問題出在哪了呢?
最終找到了一個iOS集成Unity項目的工程 https://github.com/Unity-Technologies/uaal-example ,最終才理清楚調用順序。
正確的退出順序:
1.2.1、遵守協議注冊監聽
1、首先是所在的控制器要遵守協議 <UnityFrameworkListener>,監聽Unity狀態的變化
2、 注冊監聽 [[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、創建一個能與iOS交互的Unity項目
2.1、引用接口文件遇到的鏈接錯誤
由于Unity-iPhone項目是另一個部門提供的,提供的接口文件在iOS項目中能編譯,但是鏈接時報錯,如下圖
查閱了大量資料也沒搞清楚原因,最后在 Unity的官方文檔 中看到iOS去調用Unity有兩種方式
1、
UnitySendMessage
2、delegates
由于我們的項目要在Unity啟動前就把數據準備好,用于加載不同的內容,UnitySendMessage
這種方式只適用于Unity已經啟動好了,iOS向Unity通信,所以就要使用delegates的方式進行通信
為了調試項目,只能自己創建一個Unity項目區驗證delegates這種方式是否行得通了,之前沒有Unity項目基礎就在騰訊課堂看到一個講解的還不錯的適合Unity入門的項目
2.2、創建一個Unity項目
https://ke.qq.com/course/3101943?taid=10573792874026231
3、iOS和Unity的相互調用:
iOS與Unity相互通信的文件放在Plugins/iOS/ 目錄下
//UnityForiOSInterface.h 文件
#import <Foundation/Foundation.h>
typedef void (*ResultHandler) (const char * _Nonnull object);
//Unity退出時觸發該通知
#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回調課程的狀態給iOS
void getiOSParams(ResultHandler resultHandler); //Unity調用iOS,iOS通過函數指針參數賦值的方式向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發送消息
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接口再調用ObjC的代碼,UnityCallObjc腳本為什么這么寫,Unity3D與iOS的交互,講得非常清楚了,具體可以看一下.
參考資料:
https://github.com/Unity-Technologies/uaal-example.git
https://ke.qq.com/course/3101943?taid=10573792874026231