Unity3d+EasyAR项目整合到原生iOS项目教程    

近段时间应公司战略要求,开始研究AR相关的技术。在试用了高通的Vuforia,开源的ARToolkit过后,都觉得这些AR SDK不甚易用。一次偶然的机会,发现了国产的EasyAR,在跑了几个官方Demo过后,立马敲定,就是它了!

    EasyAR SDK分为两个版本,一个是Unity3d的版本,另一个是iOS及Android 的原生版本。不过原生版本由于没有接入任何3D引擎,直接使用OpenGL ES的话,代价极高,而且相当难以进行跨平台开发,因此,Unity3d 版本是我们的首选。然而这也带来一个严重的问题:Unity3d运行在iOS上,是直接生成Xcode项目,然后利用Xcode编译运行的;而我们需要的是AR功能是作为原生APP的一部分,因此肯定不能直接使用Unity3d生成的项目。那么就进入到了本文的主题,如何整合一个Unity3d项目到原生App项目。接下来,跟随笔者的步骤,使用EasyAR 官方Demo来完成本次原生项目的整合吧!

(一)导出Unity3d到iOS项目

    为了整合方便,我们先创建目录结构如下图所示:

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    其中,unity目录用来存放unity3d项目及导出的iOS工程。ios目录则是我们自己创建的iOS工程。现在,我们将EasyAR官方Demo丢进unity下的NativeUnity目录。

    然后,我们打开unity3d项目,填写EasyAR授权Key。

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    导出iOS工程。PlayerSettings的配置,参照下图,注意Bundle Identifier一定要和申请的一致。保存iOS工程到之前建好的ios-build目录。

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    好了,save,我们就能看到导出的iOS工程静静地躺那儿了

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

(二)创建自己的iOS工程

    1.创建iOS工程

    新建一个SingleView工程,保存在之前创建好的ios目录下,注意BundleID要和导出的iOS工程完全一致。

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    2.导入unity生成的iOS工程

    在创建好的iOS工程里新建一个Group,名字无所谓,易于识别就好,这里面我们将引用unity3d生成的iOS工程中的文件,主要就是三个目录,Classes,Libraries,Data。这里需要注意的是,Classes和Libraries目录我们作为Group引用,切记不要勾选copy,而Data目录不需要参与编译,作为folder引用进来即可。

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    好了,导入过后大概就是这个样子

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    如果是普通的unity工程,目录引用就到此为止了,但是EasyAR 的工程另有特殊的地方。普通的Unity工程,入口是UnityAppController,但是EasyAR把它替换成了EasyARAppController,因此我们需要再引入它。

    先删掉原本iOS这个目录(切记只能删除引用)。

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    再将Unity工程中 Assets/Plugins/iOS目录拖进xcode工程中,并删掉目录中两个meta文件的关联。

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

至此,自建的iOS工程基本定型。

(三)配置iOS工程

接下来,我们需要对工程进行配置,这里我们使用XCode8 GM版进行演示。

  1. 修改编译选项

    我们首先需要将Bitcode关闭,新版的Unity已经支持Bitcode但EasyAR并不支持,不关闭无法正常编译。

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    接下来修改修Linking参数,将Linking->Other Linker Flags的参数修改为 -weak_framework CoreMotion -weak-lSystem

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    然后修改头文件和库搜索目录,将unity生成的iOS工程中的根目录,Classes,Libraries,Libraries/libil2cpp/include,Native这些目录添加至头文件搜索路径,再将Libraries及Libraries/Plugins/iOS两个目录添加至库搜索目录,具体如下图所示:

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    修改LLVM -Custom Complier Flags->Other C Flags 添加参数 -DINIT_SCRIPTING_BACKEND=1

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    修改LLVM -Language->C Language Dialect 为C99;并新建一个PCH文件,我们这里命名为PrefixHeader.pch

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    修改LLVM -Language - C++ ->C++ Language Dialect 为C++11

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    最后,我们需要添加三项自定义设置MTL_ENABLE_DEBUG_INFO -> NO ,UNITY_RUNTIME_VERSION -> 当前你的Unity3d版本号,这里为5.4.0f3,UNITY_SCRIPTING_BACKEND->il2cpp

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

  2. 添加工程依赖

    具体如下图所示

    Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

(四)修改代码

    1.找到原工程的PCH文件,复制内容到之前建立好的PrefixHeader.pch文件中

    

    

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#endif
#include "Preprocessor.h"
#include "UnityTrampolineConfigure.h"
#include "UnityInterface.h"
#ifndef __OBJC__
#if USE_IL2CPP_PCH
#include "il2cpp_precompiled_header.h"
#endif
#endif
#ifndef TARGET_IPHONE_SIMULATOR
#define TARGET_IPHONE_SIMULATOR 0
#endif
#define printf_console printf

2.找到原工程的main.m文件,复制其内容到新建工程的main.m文件中,将main.m修改为main.mm允许C++混合编译;并替换变量AppControllerClassName为字符串"AppDelegate",作用是将Unity的入口修改为原生工程的AppDelegate。同时在Build Phases ->Compier Sources 中搜索main.mm 删掉unity生成的原工程中的main.mm。

#include "RegisterMonoModules.h"
#include "RegisterFeatures.h"
#include <csignal>

// Hack to work around iOS SDK 4.3 linker problem
// we need at least one __TEXT, __const section entry in main application .o files
// to get this section emitted at right time and so avoid LC_ENCRYPTION_INFO size miscalculation
static const int constsection = 0;

void UnityInitTrampoline();

// WARNING: this MUST be c decl (NSString ctor will be called after +load, so we cant really change its value)
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:"AppDelegate"]);
    }
    
    return 0;
}

#if TARGET_IPHONE_SIMULATOR && TARGET_TVOS_SIMULATOR

#include <pthread.h>

extern "C" int pthread_cond_init$UNIX2003(pthread_cond_t *cond, const pthread_condattr_t *attr)
{ return pthread_cond_init(cond, attr); }
extern "C" int pthread_cond_destroy$UNIX2003(pthread_cond_t *cond)
{ return pthread_cond_destroy(cond); }
extern "C" int pthread_cond_wait$UNIX2003(pthread_cond_t *cond, pthread_mutex_t *mutex)
{ return pthread_cond_wait(cond, mutex); }
extern "C" int pthread_cond_timedwait$UNIX2003(pthread_cond_t *cond, pthread_mutex_t *mutex,
                                               const struct timespec *abstime)
{ return pthread_cond_timedwait(cond, mutex, abstime); }

#endif // TARGET_IPHONE_SIMULATOR && TARGET_TVOS_SIMULATOR

Unity3d+EasyAR项目整合到原生iOS项目教程-雪银驿站

3.修改UnityAppController.h,将其中的内联方法GetAppController()修改如下:

#import "AppDelegate.h"
inline UnityAppController*	GetAppController()
{
    AppDelegate * delegate = [UIApplication sharedApplication].delegate;
	return delegate.unityController;
}

4.新建EasyARAppController.h 因为原本的EasyARAppController是没有头文件的,是通过OC运行时初始化的,为了方便在代码中初始化和使用,单独建立它的头文件。

//
//  EasyARAppController.h
//  EasyARUnityNative
//
//  Created by Kevin on 16/9/5.
//  Copyright © 2016年 Liaoyh. All rights reserved.
//
#import "UnityAppController.h"
@interface EasyARAppController : UnityAppController
@end

5.继承EasyARAppController,创建MyARAppController,方便后续开发

MyARAppController.h

//
//  MyARAppController.h
//  EasyARUnityNative
//
//  Created by Kevin on 16/9/5.
//  Copyright © 2016年 Liaoyh. All rights reserved.
//
#import "EasyARAppController.h"
@interface MyARAppController : EasyARAppController
@end

MyARAppController.mm

//
//  MyARAppController.m
//  EasyARUnityNative
//
//  Created by wk on 16/9/5.
//  Copyright © 2016年 Liaoyh. All rights reserved.
//
#import "MyARAppController.h"
#import "AppDelegate.h"
extern "C" {
    
    void backToNative(){
        
        AppDelegate* delegate =  [UIApplication sharedApplication].delegate;
        
        [delegate hideUnityWindow];
        
    }
    
}
@implementation MyARAppController
-(void)viewDidLoad{
    
    
}
@end

6.修改AppDelegate,将AppDelegate.m修改为AppDelegate.mm,并创建新的Unity3d入口

AppDelegate.h

//
//  AppDelegate.h
//  EasyARUnityNative
//
//  Created by Kevin on 16/9/5.
//  Copyright © 2016年 Liaoyh. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) UIWindow *unityWindow;

@property (strong, nonatomic) UnityAppController *unityController;

-(void)showUnityWindow;

-(void)hideUnityWindow;

@end

AppDelegate.mm

//
//  AppDelegate.m
//  EasyARUnityNative
//
//  Created by wk on 16/9/5.
//  Copyright © 2016年 Liaoyh. All rights reserved.
//

#import "AppDelegate.h"
#import "ViewController.h"
#import "MyARAppController.h"
@interface AppDelegate ()

@property (nonatomic,strong) UINavigationController * navVC;

@end

@implementation AppDelegate


-(UIWindow *)unityWindow{
    
    return UnityGetMainWindow();
    
}

-(void)showUnityWindow{
    
    [self.unityWindow makeKeyAndVisible];
    UnitySendMessage("CameraDevice", "StartCapture", "");
    
}

-(void)hideUnityWindow{
    
    [self.window makeKeyAndVisible];
    UnitySendMessage("CameraDevice", "StopCapture", "");
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    
    self.window.backgroundColor = [UIColor whiteColor];
    
    self.navVC = [[UINavigationController alloc]initWithRootViewController:[[ViewController alloc]init]];
    
    self.window.rootViewController = self.navVC;
    
    self.unityController = [[MyARAppController alloc]init];
    
//    [self.window makeKeyAndVisible];
    
    [self.unityController application:application didFinishLaunchingWithOptions:launchOptions];
    
    [self hideUnityWindow];
    
    
    return YES;
}

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

- (void)applicationDidEnterBackground:(UIApplication *)application {
    
    [self.unityController applicationDidEnterBackground:application];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
   
    [self.unityController applicationWillEnterForeground:application];
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    
    [self.unityController applicationDidBecomeActive:application];
}

- (void)applicationWillTerminate:(UIApplication *)application {
    
    [self.unityController applicationWillTerminate:application];
}

@end

7.主界面ViewController

//
//  ViewController.m
//  EasyARUnityNative
//
//  Created by wk on 16/9/5.
//  Copyright © 2016年 Liaoyh. All rights reserved.
//

#import "ViewController.h"
#import "AppDelegate.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIButton * enterBtn = [UIButton buttonWithType:UIButtonTypeSystem];
    
    enterBtn.frame = CGRectMake(0, 0, 100, 100);
    
    enterBtn.center = self.view.center;
    
    [enterBtn setTitle:@"进入" forState:UIControlStateNormal];
    
    [enterBtn addTarget:self action:@selector(didClickEnter:) forControlEvents:UIControlEventTouchUpInside];
    
    [self.view addSubview:enterBtn];
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


-(void)didClickEnter:(UIButton*)btn{
    
    AppDelegate * delegate = [UIApplication sharedApplication].delegate;
    
    [delegate showUnityWindow];
    
}


@end

总结:

    整个整合教程基本上就到此为止。总结下整合思路,其实就是参照unity3d导出的iOS工程的配置,重新建立一个新的iOS工程,引用原工程的文件,然后修改UnityAppController的启动入口到AppDelegate,利用两个不同的UIWindow分别启动UnityAppController和iOS原生Controller,来达到自由切换的目的。本教程同样适用于任何其他类型的Unity项目,希望起到一个抛砖引玉的作用,如果遇到任何问题,请在评论中留言。

参考文档:https://the-nerd.be/2015/11/13/integrate-unity-5-in-a-native-ios-app-with-xcode-7/  万分感谢此博文的作者,可以说是全网最完整的Unity3d整合iOS工程的教程。