Unity與iOS交互

Unity與iOS相互調起、交互

上一篇我們已經實現了將Unity導出的工程集成到原生iOS項目中,接下來我們來實現Native與Unity相互切換、調起。

需要注意的是,Unity一旦初始化,是不能關閉的,否則App直接就會被關閉。所以,一旦調起Unity,內存就不會降下來了。第一次啓動會比較慢,之後就很快了。另外,集成Unity之後,就只能真機運行了,所以,要準備好證書,以免不必要的麻煩。

很多文章是用了兩個UIWindow來回切換,而我並不推薦使用這種方式。另外,屏幕旋轉問題,後面我會提到。

1、之前我們已經在pch中import了UnityAppController,所以其他地方不用再import了。所有的接口建議寫在AppDelegate中。

首先,將AppDelegate.m改名爲AppDelegate.mm 
然後,在AppDelegate.h中,如下:

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UnityAppController *unityController;

- (void)showUnityWindow;

- (void)hideUnityWindow;

- (void)shouldAttachRenderDelegate;

@end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

接下來,修改AppDelegate.mm,如下: 
如有報錯,先暫時忽略。之後會填坑。

#import "AppDelegate.h"

//這裏就說明,我們必須改成.mm的了~
extern "C" void VuforiaRenderEvent(int marker);
extern "C" void VuforiaSetGraphicsDevice(void* device, int deviceType, int eventType);


@interface AppDelegate ()
{
    //這裏的兩個BOOL,是用來區分,是否第一次加載Unity,以及Unity視圖是否出現
    BOOL _notFirstShow;
    BOOL _isShowing;
}
@property (nonatomic, weak) UIImageView *ARLaunchView;

@end

@implementation AppDelegate

//這個方法應該是使用了高通就必須這樣來渲染。如果不用高通,這個方法可以空實現(不確定,遇到問題了再反饋吧,後續會更新)
- (void)shouldAttachRenderDelegate {
    //如果報錯,刪掉上面的extern "C" void VuforiaSetGraphicsDevice(void* device, int deviceType, int eventType);
    UnityRegisterRenderingPlugin(&VuforiaSetGraphicsDevice, &VuforiaRenderEvent);
    //下面這一行先不寫,如果上面的報錯,就刪掉上面那一行,調用下面這一行的
    UnityRegisterRenderingPlugin(NULL, &VuforiaRenderEvent);
    //如果兩個都報錯,那就都刪掉,這個方法就做空實現。上面那兩個extern "C"都可以刪掉了
}

- (void)showUnityWindow {
    //這裏的圖片就是自定義的Unity啓動圖,因爲第一次啓動會很慢,有個啓動圖會好一點
    UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.window.height, self.window.width)];
    imgView.image = [UIImage imageNamed:@"AR_launch"];
    [self.window.rootViewController.view addSubview:imgView];
    //因爲Unity是強制橫屏的,所以這裏要把imageView旋轉
    imgView.transform = CGAffineTransformMakeRotation(M_PI_2);
    imgView.center = CGPointMake(self.window.center.x, self.window.center.y);
    self.ARLaunchView = imgView;
    //這裏必須延遲執行,否則圖片不會出現
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (!_notFirstShow) {
            //第一次啓動
            [self.unityController startUnityFirstTime];
            _notFirstShow = YES;
        } else {
            //已經初始化
            [self.unityController startUnityOtherTime];
        }
        _isShowing = YES;
    });
}

- (void)hideUnityWindow {
    [self.unityController doExitSelector];
    [self.ARLaunchView removeFromSuperview];
    //看到這裏就明白了,我用的是模態的方式展示的~哈哈哈
    [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
    _isShowing = NO;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //這裏我是用代碼加載的Window,沒有用StoryBoard    
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.backgroundColor = [UIColor whiteColor];

    self.unityController = [[UnityAppController alloc] init];
    [self.unityController application:application didFinishLaunchingWithOptions:launchOptions];

    //這裏+1是因爲我工程中的TextField長按的時候那個放大鏡的問題。
    self.window.windowLevel = UIWindowLevelNormal + 1;
    //這裏設置你們自己的根控制器
    self.window.rootViewController = [HRAccountTool chooseRootViewController];
    [self.window makeKeyAndVisible];

    return YES;
}

#pragma mark - UIApplicationDelegate
- (void)applicationWillResignActive:(UIApplication *)application {
    [self.unityController applicationWillResignActive:application];
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
    [self.unityController applicationDidEnterBackground:application];
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    [self.unityController applicationWillEnterForeground:application];
    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    [self.unityController applicationDidBecomeActive:application];
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    if (_notFirstShow && !_isShowing) {
        //如果unity處於暫停狀態,從後臺喚醒時也要保持暫停狀態
        [self.unityController doExitSelector];
    }
}


- (void)applicationWillTerminate:(UIApplication *)application {
    [self.unityController applicationWillTerminate:application];
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

譁…AppDelegate終於改完了!然而我們只完成了三分之一的工作。繼續吧…耐心點。

2、現在來修改UnityAppController

在Class目錄下,修改UnityAppController.h,在- (void)startUnity:(UIApplication*)application; 方法下面,聲明3個方法,是我們自定義的。 
看到這裏大概你們就明白了。我們要懶加載Unity組件,App啓動的時候並不去加載,而是等到需要跳轉的時候才加載。所以要把第一次跳轉和其他分開。

//自定義開啓關閉Unity
- (void)startUnityFirstTime;
- (void)startUnityOtherTime;
- (void)doExitSelector;
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

找到如下方法:

inline UnityAppController*  GetAppController()
{
    return (UnityAppController*)[UIApplication sharedApplication].delegate;
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

替換爲:

inline UnityAppController*  GetAppController()
{
    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    return delegate.unityController;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

所以我們在AppDelegate.h中聲明瞭這個屬性~

重點來了!接下來修改UnityAppController.mm 
在它引用頭文件的最後一行,引入自定義控制器,我們暫且叫“ARViewController”,什麼?沒有?你不會創建啊!

#include "PluginBase/AppDelegateListener.h"
//我是放在這一行下面的
#import "ARViewController.h"
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

接下來,找到

- (void)shouldAttachRenderDelegate  {}
  • 1
  • 1

我們要實現它

- (void)shouldAttachRenderDelegate  {
    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    [delegate shouldAttachRenderDelegate];
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

所以我們在AppDelegate.h中聲明瞭這個方法,並在.mm中實現了它。 
找到這個方法,默認實現是這樣的:

- (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(startUnity:) withObject: application afterDelay: 0];
    }

    _didResignActive = false;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

我們要修改這個方法!

//首先在這個方法上面聲明一個bool變量
bool homePageEnable = true;
//修改這個方法
- (void)applicationDidBecomeActive:(UIApplication*)application
{
    ::printf("-> applicationDidBecomeActive()\n");

    if(_snapshotView)
    {
        [_snapshotView removeFromSuperview];
        _snapshotView = nil;
    }
    if (homePageEnable) {
        homePageEnable = false;
        [self performSelector:@selector(startHomePage:) withObject:application afterDelay:0];
    }
    if(_unityAppReady)
    {
        if(UnityIsPaused())
        {
            UnityPause(0);
            UnityWillResume();
        }
        UnitySetPlayerFocus(1);
    }
    else if(!_startUnityScheduled)
    {
        _startUnityScheduled = true;
    }

    _didResignActive = false;
}

- (void)startHomePage:(UIApplication *)application {
    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    [delegate.window makeKeyAndVisible];
}

- (void)startUnityFirstTime {
    [self startUnity:[UIApplication sharedApplication]];
     [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:[[ARViewController alloc] init] animated:YES completion:nil];
}

- (void)startUnityOtherTime {
     [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:[[ARViewController alloc] init] animated:YES completion:nil];
    if (_didResignActive) {
        UnityPause(false);
    }
    _didResignActive = false;
}

- (void)doExitSelector {
    UnityPause(true);
    _didResignActive = true;
    Profiler_UninitProfiler();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

終於改完啦!已經完成了三分之二。

3、這裏先說一個問題,如果你的工程只支持豎屏,需要在你的window的根控制器,實現以下三個方法:

注:如果只支持豎屏,才改這個。其他情況請跳過第3步。

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskPortrait;
}

- (BOOL)shouldAutorotate {
    //如果設置了只支持豎屏,一定要return NO
    return NO;
}

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationPortrait;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

之後,找到UnityViewControllerBase.mm文件,找到這個方法,改爲return NO

- (BOOL)shouldAutorotate
{
    return NO;
}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

4、修改ARViewController.m

實現

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self.view addSubview:UnityGetMainWindow().rootViewController.view];
    [self addChildViewController:UnityGetMainWindow().rootViewController];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果設置了只支持豎屏,才實現以下三個方法,否則請跳過下面這一步。

- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
    return UIInterfaceOrientationLandscapeRight;
}

- (BOOL)shouldAutorotate {
    return NO;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
    return UIInterfaceOrientationMaskLandscapeRight;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

5、調起Unity、關閉Unity、相互傳值(交互)

調起Unity: 
調起是很方便的,因爲是從原生跳轉過去嘛,比如點擊某個按鈕,那麼只需在按鈕的點擊方法中調用即可:

AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[delegate showUnityWindow];
  • 1
  • 2
  • 1
  • 2

這樣就會模態出來Unity的界面,而且是用根控制器模態的。當然你也可以用其他控制器。

關閉Unity: 
接下來是關閉Unity,對於返回原生,我建議用Unity調Native,其實就是實現一個C語言方法,當然這個要在Unity導出工程之前,就寫好。建議在Libraries/Plugins/iOS目錄中,添加專門用於Native與Unity交互的.h和.mm,舉個例子,在.mm中:

#import "NativeUnity.h"

#if defined(__cplusplus)
extern "C"{
#endif
    void UnityCallIOS(){

    }

    void PauseUnity(){
        [[NSNotificationCenter defaultCenter] postNotificationName:@"UnityWantToExit" object:nil];
    }

#if defined(__cplusplus)
}
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

這裏的兩個函數,UnityCallIOS()和PauseUnity()都是Native和Unity約定好的。說通俗一點,就是Unity調用,Native實現。 
這個地方就是典型的Unity調Native。 
在上面的交互中,通過Unity調用OC,然後我發出了一條通知。 
我是在根控制器監聽通知的,爲何要在根控制器,因爲Unity被調起的時候,它一定存在。 
這裏我使用了RAC監聽通知,當然可以換成普通的監聽通知,不過別忘了移除通知昂。

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"UnityWantToExit" object:nil] subscribeNext:^(NSNotification * _Nullable x) {
    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    [delegate hideUnityWindow];
}];
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

那麼Native調Unity呢?更簡單,只需一句代碼 
這其實類似於iOS的performSelector:方法。

//第一個參數:消息的接受者,或者說誰來執行方法
//第二個參數:函數名
//第三個參數:需要傳的值

UnitySendMessage("SceneMain", "MethodName", "What the fuck?");

//注意,三個參數都是C語言字符串,沒有@哦 testDemo:鏈接: http://pan.baidu.com/s/1kVgHVYR 密碼: maw6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章