iOS與Unity3d交互

前言

  • 最近在實驗室做了一個項目,用到了藍牙通訊和U3D的交互,都有很多坑,如:IOS與Unity3D界面之間的跳轉、數據的傳遞等操作。過程其實不是很難,只是比較繁瑣,剛開始可能一頭霧水,慢慢學習了解后,就會發現其實很簡單。
  • 藍牙的一些知識在這里:IOS藍牙通訊詳解
  • 整篇文章主要介紹以下兩點
  1. IOS與Unity之間的界面切換
  2. IOS與Unity之間的函數調用以及傳值

IOS與Unity之間的界面切換

在IOS和Unity3D跨平臺開發中,基本就是兩個平臺之間的數據交互和界面切換。而界面交互是重中之重。利用Unity3D可以實現一些IOS原生界面所不具有的效果,而IOS原生界面則在整個程序界面中又顯得更和諧一些。所以無論是功能還是美觀,兩者之間的界面交互都很常用。

前期Unity準備

我們要用Unity和IOS的界面切換和數據交互,就必然要先實現一個Unity程序。我就默認讀者都會一些Unity基礎和OC技術吧。

  • 首先建立一個新的Unity程序,在場景中拖入一個Cube。我們可以利用這個cube來展示Unity和IOS之間的數據交互。


    一個簡單的Unity場景
  • 我們在Camera下建立邦定一個腳本,在腳本中提供各類接口。
一個簡單的測試腳本
  • 我們編輯腳本,給出一些基本的接口。
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class Test : MonoBehaviour {
    public GameObject cube;

    // DllImport這個方法相當于是告訴Unity,有一個unityToIOS函數在外部會實現。
    // 使用這個方法必須要導入System.Runtime.InteropServices;
    [DllImport("__Internal")]
    private static extern void unityToIOS (string str);

    void OnGUI()
    {
        // 當點擊按鈕后,調用外部方法
        if (GUI.Button (new Rect (100, 100, 100, 30), "跳轉到IOS界面")) {
            // Unity調用ios函數,同時傳遞數據
            unityToIOS ("Hello IOS");
        }
    }
    // 向右轉函數接口
    void turnRight(string num){
        float f;
        if (float.TryParse (num, out f)) {// 將string轉換為float,IOS傳遞數據只能用以string類型
            Vector3 r = new Vector3 (cube.transform.rotation.x, cube.transform.rotation.y - 10f, cube.transform.rotation.z);
            cube.transform.Rotate (r);
        }
    }
    // 向左轉函數接口
    void turnLeft(string num){
        float f;
        if (float.TryParse (num, out f)) {// 將string轉換為float,IOS傳遞數據只能用以string類型
            Vector3 r = new Vector3 (cube.transform.rotation.x, cube.transform.rotation.y - 10f, cube.transform.rotation.z);
            cube.transform.Rotate (r);
        }
    }
}
  • 在給出接口之后,我們將Unity工程導出到IOS工程,點擊File->Build Settings->IOS,(在build之前,我們需要填寫一些ID信息。其實這類信息也可以不現在填,在導出工程后,可以在Xcode里面填寫)然后點擊build。
導出工程到Xcode

從IOS界面切換到Unity界面

  • 從IOS界面切換到Unity界面,需要做的是攔截Unity界面的啟動,當我們需要加載Unity界面的時候才讓其啟動。而要攔截Unity界面,就需要先搞明白Unity界面加載的順序。main.mm文件是整個Unity工程的入口。在main函數中先進行一些初始化和注冊操作,然后執行UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String:AppControllerClassName]),這一句就執行了UnityAppController對象。在UnityAppController中,Unity整個程序就算是啟動起來了。所以我們需要了解UnityAppController對象中一些方法的執行順序以便于我們攔截Unity界面。在UnityAppController這個對象中,一些核心的方法已經有打印,如果想要知道所有方法的執行順序,我們可以在每一個方法里面添加打印方法,此處為了簡單,我就利用已經有的打印函數。
const char* AppControllerClassName = "UnityAppController";
int main(int argc, char* argv[])
{
    @autoreleasepool
    {
        UnityInitTrampoline();
        UnityParseCommandLine(argc, argv);
        RegisterMonoModules();
        NSLog(@"-> registered mono modules %p\n", &constsection);
        RegisterFeatures();

        // iOS terminates open sockets when an application enters background mode.
        // The next write to any of such socket causes SIGPIPE signal being raised,
        // even if the request has been done from scripting side. This disables the
        // signal and allows Mono to throw a proper C# exception.
        std::signal(SIGPIPE, SIG_IGN);

        UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String:AppControllerClassName]);
    }

    return 0;
}
  • 運行項目后,我們看到打印
打印結果
  • - (void)applicationDidBecomeActive:(UIApplication*)application這個方法中,執行了startUnity:方法。而這個方法一旦執行,Unity界面就啟動起來了。所以我們需要做的攔截操作就在startUnity:之前,即- (void)applicationDidBecomeActive:(UIApplication*)application方法中調用IOS原生界面。
  • 觀察一下- (void)applicationDidBecomeActive:(UIApplication*)application方法,中間有一句[self performSelector:@selector(startUnity:) withObject:application afterDelay:0];,我們要做的就是把startUnity替換成我們自己的方法,在自己的方法中調用IOSUI,然后在UI中寫一些邏輯來讓IOS可以跳轉到Unity界面。
  - (void)applicationDidBecomeActive:(UIApplication*)application
{
    ::printf("-> applicationDidBecomeActive()\n");
    [self removeSnapshotView];
    if(_unityAppReady)
    {
        if(UnityIsPaused() && _wasPausedExternal == false)
        {
            UnityWillResume();
            UnityPause(0);
        }
        UnitySetPlayerFocus(1);
}
    else if(!_startUnityScheduled)
    {
        _startUnityScheduled = true;
        [self performSelector:@selector(startSelfIOSView) withObject:application afterDelay:0];
    }
    _didResignActive = false;
}
- (void)startSelfIOSView
{
    UIViewController *vc = [[UIViewController alloc] init];
    vc.view.backgroundColor = [UIColor blueColor];
    vc.view.frame = [UIScreen mainScreen].bounds;
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 70, 30)];
    btn.backgroundColor = [UIColor whiteColor];
    [btn setTitle:@"跳轉到Unity界面" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(startUnity:) forControlEvents:UIControlEventTouchUpInside];
    [vc.view addSubview:btn];
    [_window addSubview:vc.view];
}
  • 運行結果,中間IOS界面運行的時候,我點擊了白色的按鈕跳轉。
IOS界面跳轉到Unity界面

從Unity界面跳轉到IOS界面

在Unity腳本中,我們已經添加了一個按鈕,并且給了接口。所以我們可以利用這個按鈕實現跳轉到IOS界面。

在IOS工程中調用Unity函數的方式是利用C語言接口。在IOS工程中利用形如

extern "C"
{
    void functionName(parameter){
    // do something
    }
}

這樣的形式來調用Unity中的函數。

因為Unity界面跳轉到IOS界面涉及到了暫停Unity所以我們需要實現一個單例來判斷Unity的暫停或啟動

//  LARManager.m
//  Unity-iPhone
//
//  Created by 柳鈺柯 on 2016/12/15.
//
//
#import "LARManager.h"
@implementation LARManager
+ (instancetype)sharedInstance
{
    static LARManager *manager;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[self alloc] init];
    });
    return manager;
}
- (instancetype)init
{
    if (self = [super init]) {
        self.unityIsPaused = NO;
        NSLog(@"單例初始化成功");
    }
    return self;
}
@end
  • 跳轉到IOS界面,我們需要實現在Unity腳本中聲明的unityToIOS函數。值得注意的是:在extern "C"中,不能用OC的selfself.window獲取到appControllerwindow,必須使用UnityAppController對象提供的方法GetAppController()UnityGetGLView()來獲取。因為要返回之前的界面,所以我需要聲明一個強引用變量來引用之前的view。在UnityAppController.h中聲明@property (strong, nonatomic) UIViewController *vc,然后在剛才的startSelfIOSView中添加一句self.vc = vc;
實現單例之后的startSelfIOSView和unityToIOS函數
- (void)startSelfIOSView
{
    // 單例變量unity沒有暫停,設置為no
    [LARManager sharedInstance].unityIsPaused = NO;
    // IOS原生界面
    UIViewController *vc = [[UIViewController alloc] init];
    vc.view.backgroundColor = [UIColor blueColor];
    vc.view.frame = [UIScreen mainScreen].bounds;
    // 添加按鈕
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 200, 30)];
    btn.backgroundColor = [UIColor whiteColor];
    btn.titleLabel.backgroundColor = [UIColor blackColor];
    [btn setTitle:@"跳轉到Unity界面" forState:UIControlStateNormal];
    [btn addTarget:self action:@selector(startUnity:) forControlEvents:UIControlEventTouchUpInside];
    // 在界面上添加按鈕
    [vc.view addSubview:btn];
    self.vc = vc;
    NSLog(@"設置界面為IOS界面");
    self.window.rootViewController = vc;
}
extern "C"
{
    // 對Unity中的unityToIOS方法進行實現
    void unityToIOS(char* str){
        // Unity傳遞過來的參數
        NSLog(@"%s",str);
        UnityPause(true);
        // 跳轉到IOS界面,Unity界面暫停
        [LARManager sharedInstance].unityIsPaused = YES;
        // GetAppController()獲取appController,相當于self
        // UnityGetGLView()獲取UnityView,相當于_window
        // 點擊按鈕后跳轉到IOS界面,設置界面為IOS界面
        GetAppController().window.rootViewController = GetAppController().vc;
    }
}
  • 實現效果:
Unity和IOS互相跳轉

IOS與Unity之間的函數調用以及傳值

在上一步中,其實我們已經實現了IOS和Unity函數調用和一部分傳值。現在更詳細的說明一下。

Unity調用IOS函數并進行傳值

  • Unity調用IOS函數其實就是先在Unity腳本中聲明一個函數接口,然后在IOS程序中實現。其中
    [DllImport("__Internal")]
    private static extern void functionName (ParameterType Parameter);

為固定格式。這一句表明會在外部引用一個叫functionName (ParameterType Parameter)的函數,參數為Parameter。在IOS程序中實現上一步中已經講過,不再贅述。

IOS調用Unity函數并進行傳值

IOS調用Unity函數需要用到UnitySendMessage方法,方法中有三個參數

UnitySendMessage("gameobject", "Method",msg);
向unity發送消息
參數一為unity腳本掛載的gameobject
參數二為unity腳本中要調用的方法名
參數三為傳遞的數據,*注意:傳遞的數據只能是char 類型

我們利用在Unity工程中已經寫好的接口來試試數據的傳遞。

  • 首先在我們需要添加2個按鈕,按鈕需要實現點擊后,傳遞數據過去,使視野中的cube(正方體)向左向右旋轉。
  • 我們只需要在原生的Unity界面中添加按鈕。在原生界面中添加和在ios工程中添加是差不多的。在界面還沒有顯示的時候,添加進去即可,我在- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions這和方法中添加按鈕。代碼如下:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    /*省略了一些Unity的操作*/
  /*.....*/
    [self createUI];
    [self preStartUnity];
  // 添加右旋按鈕
  UIButton *rightBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, 150, 100, 30)];
  rightBtn.backgroundColor = [UIColor whiteColor];
  [rightBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  [rightBtn setTitle:@"向右旋轉" forState:UIControlStateNormal];
  [rightBtn addTarget:self action:@selector(turnRight) forControlEvents:UIControlEventTouchUpInside];
  self.rightBtn = rightBtn;
  // 添加左旋按鈕
  UIButton *leftBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, 200, 100, 30)];
  leftBtn.backgroundColor = [UIColor whiteColor];
  [leftBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
  [leftBtn setTitle:@"向左旋轉" forState:UIControlStateNormal];
  [leftBtn addTarget:self action:@selector(turnLeft) forControlEvents:UIControlEventTouchUpInside];
  self.leftBtn = leftBtn;
  // 在Unity界面添加按鈕
  [UnityGetGLViewController().view addSubview:_rightBtn];
  [UnityGetGLViewController().view addSubview:_leftBtn];
    // if you wont use keyboard you may comment it out at save some memory
    [KeyboardDelegate Initialize];
    return YES;
}
// 左旋按鈕響應事件
- (void)turnRight
{
    const char* str = [[NSString stringWithFormat:@"10"] UTF8String];
    UnitySendMessage("Main Camera", "turnRight", str);
}
// 右旋按鈕響應事件
- (void)turnLeft
{
    const char* str = [[NSString stringWithFormat:@"10"] UTF8String];
    UnitySendMessage("Main Camera", "turnLeft", str);
}
  • 在添加完成按鈕后,我們向攝像機發送消息。因為我們的腳本是綁定在主攝像頭上的,所以第一個參數為主攝像機的名字,第二個參數為腳本中的方法,第三個參數為數據。
  • 因為我個人粗心,在Unity腳本中,關于旋轉的加減號忘記修改了,導致點擊左旋按鈕后物體也是右旋。只需要把腳本中turnLeft函數的-號改成+號就行了。運行如下:
IOS調用函數以及傳值到Unity

總結

  • IOS切換到Unity的界面切換主要通過攔截Unity界面的運行,在我們想要使用Unity界面的時候才讓Unity界面的方法繼續執行。IOS界面切換到Unity界面,如果Unity界面不是第一次執行,記得執行UnityPause(false)方法,因為我們在切換到IOS的時候,會暫停Unity界面
  • Unity切換到IOS界面將viewController更改成自已定義的界面控制器就可以了。切換到IOS界面的時候記得執行UnityPause(true)方法
  • IOS傳遞數據到Unity界面主要通過UnitySendMessage()方法,*參數一為unity腳本掛載的gameobject,參數二為unity腳本中要調用的方法名,第三個參數為數據,數據格式只能為char **,利用這個方法也可以調用Unity的函數。
  • Unity傳遞數據到IOS也是通過聲明外部函數,通過在Unity腳本調用外部函數的時候以參數的方式傳遞數據。
  • 聲明外部函數的格式為:
  // 聲明外部函數
    [DllImport("__Internal")]
    private static extern void functionName (ParameterType Parameter);
  • IOS實現Unity中聲明的函數格式為:
extern "C"
{
    void functionName(parameter){
    // do something
    }
}

結語

  • 最近咨詢IOS和Unity通訊的人也有點多,寫出這個博客給大家作為參考。
  • 跨平臺通訊都是自己在琢磨,錯誤難免,敬請指正。
  • 有疑問可以給我發郵件,但是在發送郵件前,請保證您深思過此問題。若問題沒有意義或者在博客中有的話,我不會進行回復,請諒解。郵箱:lyk82465@gmail.com
  • 最后附上Demo,點擊下載。Unity和IOS交互Demo
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一.小記 最近公司有個需求,需要用unity3D建立3D模型,并且實現切換功能,由于做開發時間不算長,又沒有接觸過...
    矯炎圻閱讀 12,858評論 20 19
  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協議。它實...
    香橙柚子閱讀 24,111評論 8 183
  • 最近一直在山西右玉跟組拍戲。我們劇組住的是大洋賓館。我們平時習慣把大洋賓館簡稱為大洋。附近沒有什么大的超市,平時收...
    戀影孤花閱讀 325評論 0 1
  • 又深了的情, 又傷的心。 他不止一次的說他愛上了一個人, 我卻又不止一次的見他, 醉的和屎一樣。 他是一個值得深交...
    非主流韭零后閱讀 100評論 3 1
  • 我問過萬物的名字 卻不知怎樣向他們提起,你 我沉默里的歡笑,歡笑里的郁郁 多少次回頭凝視,人群都已散去 神分了我的...
    白前山茶閱讀 319評論 0 0