转自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语言字符串,没有@哦