Unity与iOS相互调起、交互的另一种方式

转自http://blog.csdn.net/qinqi376990311/article/details/72318037

Unity与iOS相互调起、交互

上一篇我们已经实现了将Unity导出的工程集成到原生iOS项目中,接下来我们来实现Native与Unity相互切换、调起。

需要注意的是,Unity一旦初始化,是不能关闭的,否则App直接就会被关闭。所以,一旦调起Unity,内存就不会降下来了。第一次启动会比较慢,之后就很快了。另外,集成Unity之后,就只能真机运行了,所以,要准备好证书,以免不必要的麻烦。

很多文章是用了两个UIWindow来回切换,而我并不推荐使用这种方式。另外,屏幕旋转问题,后面我会提到。

1、之前我们已经在pch中import了UnityAppController,所以其他地方不用再import了。所有的接口建议写在AppDelegate中。

首先,将AppDelegate.m改名为AppDelegate.mm

然后,在AppDelegate.h中,如下:

#import@interfaceAppDelegate:UIResponder@property(strong,nonatomic)UIWindow*window;@property(strong,nonatomic) UnityAppController *unityController;- (void)showUnityWindow;- (void)hideUnityWindow;- (void)shouldAttachRenderDelegate;@end

接下来,修改AppDelegate.mm,如下:

如有报错,先暂时忽略。之后会填坑。

#import"AppDelegate.h"//这里就说明,我们必须改成.mm的了~extern"C"voidVuforiaRenderEvent(intmarker);extern"C"voidVuforiaSetGraphicsDevice(void* device,intdeviceType,inteventType);@interfaceAppDelegate(){//这里的两个BOOL,是用来区分,是否第一次加载Unity,以及Unity视图是否出现BOOL_notFirstShow;BOOL_isShowing;}@property(nonatomic,weak)UIImageView*ARLaunchView;@end@implementationAppDelegate//这个方法应该是使用了高通就必须这样来渲染。如果不用高通,这个方法可以空实现(不确定,遇到问题了再反馈吧,后续会更新)- (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 = [[UIImageViewalloc] initWithFrame:CGRectMake(0,0,self.window.height,self.window.width)];    imgView.image= [UIImageimageNamed:@"AR_launch"];    [self.window.rootViewController.viewaddSubview: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.unityControllerstartUnityFirstTime];            _notFirstShow =YES;        }else{//已经初始化[self.unityControllerstartUnityOtherTime];        }        _isShowing =YES;    });}- (void)hideUnityWindow {    [self.unityControllerdoExitSelector];    [self.ARLaunchViewremoveFromSuperview];//看到这里就明白了,我用的是模态的方式展示的~哈哈哈[self.window.rootViewControllerdismissViewControllerAnimated:YEScompletion:nil];    _isShowing =NO;}- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {//这里我是用代码加载的Window,没有用StoryBoardself.window= [[UIWindowalloc] initWithFrame:[UIScreen mainScreen].bounds];self.window.backgroundColor= [UIColorwhiteColor];self.unityController= [[UnityAppController alloc] init];    [self.unityControllerapplication:application didFinishLaunchingWithOptions:launchOptions];//这里+1是因为我工程中的TextField长按的时候那个放大镜的问题。self.window.windowLevel= UIWindowLevelNormal +1;//这里设置你们自己的根控制器self.window.rootViewController= [HRAccountTool chooseRootViewController];    [self.windowmakeKeyAndVisible];returnYES;}#pragma mark - UIApplicationDelegate- (void)applicationWillResignActive:(UIApplication*)application {    [self.unityControllerapplicationWillResignActive:application];}- (void)applicationDidEnterBackground:(UIApplication*)application {    [self.unityControllerapplicationDidEnterBackground: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.unityControllerapplicationWillEnterForeground: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.unityControllerapplicationDidBecomeActive: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.unityControllerdoExitSelector];    }}- (void)applicationWillTerminate:(UIApplication*)application {    [self.unityControllerapplicationWillTerminate:application];// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.}@end

哗…AppDelegate终于改完了!然而我们只完成了三分之一的工作。继续吧…耐心点。

2、现在来修改UnityAppController

在Class目录下,修改UnityAppController.h,在- (void)startUnity:(UIApplication*)application;方法下面,声明3个方法,是我们自定义的。

看到这里大概你们就明白了。我们要懒加载Unity组件,App启动的时候并不去加载,而是等到需要跳转的时候才加载。所以要把第一次跳转和其他分开。

//自定义开启关闭Unity-(void)startUnityFirstTime;-(void)startUnityOtherTime;-(void)doExitSelector;

找到如下方法:

inlineUnityAppController*  GetAppController(){return(UnityAppController*)[UIApplication sharedApplication].delegate;}

替换为:

inlineUnityAppController*  GetAppController(){    AppDelegate *delegate= (AppDelegate *)[UIApplication sharedApplication].delegate;returndelegate.unityController;}

所以我们在AppDelegate.h中声明了这个属性~

重点来了!接下来修改UnityAppController.mm

在它引用头文件的最后一行,引入自定义控制器,我们暂且叫“ARViewController”,什么?没有?你不会创建啊!

#include"PluginBase/AppDelegateListener.h"//我是放在这一行下面的#import"ARViewController.h"

接下来,找到

-(void)shouldAttachRenderDelegate  {}

我们要实现它

- (void)shouldAttachRenderDelegate  {    AppDelegate *delegate= (AppDelegate *)[UIApplication sharedApplication].delegate;    [delegateshouldAttachRenderDelegate];}

所以我们在AppDelegate.h中声明了这个方法,并在.mm中实现了它。

找到这个方法,默认实现是这样的:

- (void)applicationDidBecomeActive:(UIApplication*)application{    ::printf("-> applicationDidBecomeActive()\n");    [selfremoveSnapshotView];if(_unityAppReady)    {if(UnityIsPaused() && _wasPausedExternal ==false)        {            UnityWillResume();            UnityPause(0);        }        UnitySetPlayerFocus(1);    }elseif(!_startUnityScheduled)    {        _startUnityScheduled =true;        [selfperformSelector:@selector(startUnity:) withObject: application afterDelay:0];    }    _didResignActive =false;}

我们要修改这个方法!

//首先在这个方法上面声明一个bool变量boolhomePageEnable =true;//修改这个方法- (void)applicationDidBecomeActive:(UIApplication*)application{    ::printf("-> applicationDidBecomeActive()\n");if(_snapshotView)    {        [_snapshotView removeFromSuperview];        _snapshotView =nil;    }if(homePageEnable) {        homePageEnable =false;        [selfperformSelector:@selector(startHomePage:) withObject:application afterDelay:0];    }if(_unityAppReady)    {if(UnityIsPaused())        {            UnityPause(0);            UnityWillResume();        }        UnitySetPlayerFocus(1);    }elseif(!_startUnityScheduled)    {        _startUnityScheduled =true;    }    _didResignActive =false;}- (void)startHomePage:(UIApplication*)application {    AppDelegate *delegate = (AppDelegate *)[UIApplicationsharedApplication].delegate;    [delegate.windowmakeKeyAndVisible];}- (void)startUnityFirstTime {    [selfstartUnity:[UIApplicationsharedApplication]];    [[UIApplicationsharedApplication].keyWindow.rootViewControllerpresentViewController:[[ARViewController alloc] init] animated:YEScompletion:nil];}- (void)startUnityOtherTime {    [[UIApplicationsharedApplication].keyWindow.rootViewControllerpresentViewController:[[ARViewController alloc] init] animated:YEScompletion:nil];if(_didResignActive) {        UnityPause(false);    }    _didResignActive =false;}- (void)doExitSelector {    UnityPause(true);    _didResignActive =true;    Profiler_UninitProfiler();}

终于改完啦!已经完成了三分之二。

3、这里先说一个问题,如果你的工程只支持竖屏,需要在你的window的根控制器,实现以下三个方法:

注:如果只支持竖屏,才改这个。其他情况请跳过第3步。

- (UIInterfaceOrientationMask)supportedInterfaceOrientations {returnUIInterfaceOrientationMaskPortrait;}- (BOOL)shouldAutorotate {//如果设置了只支持竖屏,一定要return NOreturnNO;}- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {returnUIInterfaceOrientationPortrait;}

之后,找到UnityViewControllerBase.mm文件,找到这个方法,改为return NO

- (BOOL)shouldAutorotate{returnNO;}

4、修改ARViewController.m

实现

- (void)viewDidLoad {    [super viewDidLoad];    //Doanyadditional setupafterloading theview.    [self.viewaddSubview:UnityGetMainWindow().rootViewController.view];[self addChildViewController:UnityGetMainWindow().rootViewController];}

如果设置了只支持竖屏,才实现以下三个方法,否则请跳过下面这一步。

-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {return UIInterfaceOrientationLandscapeRight;}-(BOOL)shouldAutorotate {return NO;}-(UIInterfaceOrientationMask)supportedInterfaceOrientations {return UIInterfaceOrientationMaskLandscapeRight;}

5、调起Unity、关闭Unity、相互传值(交互)

调起Unity:

调起是很方便的,因为是从原生跳转过去嘛,比如点击某个按钮,那么只需在按钮的点击方法中调用即可:

AppDelegate *delegate= (AppDelegate *)[UIApplication sharedApplication].delegate;[delegateshowUnityWindow];

这样就会模态出来Unity的界面,而且是用根控制器模态的。当然你也可以用其他控制器。

关闭Unity:

接下来是关闭Unity,对于返回原生,我建议用Unity调Native,其实就是实现一个C语言方法,当然这个要在Unity导出工程之前,就写好。建议在Libraries/Plugins/iOS目录中,添加专门用于Native与Unity交互的.h和.mm,举个例子,在.mm中:

#import "NativeUnity.h"#ifdefined(__cplusplus)extern"C"{#endifvoidUnityCallIOS(){    }voidPauseUnity(){        [[NSNotificationCenter defaultCenter] postNotificationName:@"UnityWantToExit"object:nil];    }#ifdefined(__cplusplus)}#endif

这里的两个函数,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;    [delegatehideUnityWindow];}];

那么Native调Unity呢?更简单,只需一句代码

这其实类似于iOS的performSelector:方法。

//第一个参数:消息的接受者,或者说谁来执行方法//第二个参数:函数名//第三个参数:需要传的值UnitySendMessage("SceneMain","MethodName","What the fuck?");//注意,三个参数都是C语言字符串,没有@哦

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容