我正在参加「启航计划」

前言

最近虚拟引擎还是很火的,QQ超级秀,淘宝人生,抖音仔仔,玩的都是虚拟偶像,那假如咱们 APP 假如也想做相似的功用,那咱们好做吗,有没有什么优缺点,用什么计划比较好,这些都值得咱们去评论一下。由于没有接触过 UE4 ,本文仅评论 unity 计划怎么嵌入运用,怎么协议交互,以及带来的问题。

Unity导入iOS工程

其实 unity 导出的包,也是一个 Target,那咱们这儿选用的计划是把 Target 接入到咱们项目工程,详细看业务决议,有些是用iOS SDK 嵌入到 unity 的 iOS 包,这个不在本文评论范围内。本来是一个 Target ,咱们工程也是一个 Target ,这时分咱们就能够经过 workspace 来增加到一起。

首要咱们把 unity 包放到咱们工程下面。

iOS嵌入虚拟引擎unity3d

然后,咱们在项目工程中增加 Unity-iPhone.xcodepro

iOS嵌入虚拟引擎unity3d

把 unity 工程导入到项目中。

iOS嵌入虚拟引擎unity3d

咱们还需求更改一些项目装备。

首要,咱们需求对 unity 工程的 bitcode 设置为 NO。 然后 Data 文件夹勾上 UnityFramework 。

iOS嵌入虚拟引擎unity3d

最终,咱们需求把 NativeCallProxy.h文件更改unityFramework权限为Public

iOS嵌入虚拟引擎unity3d

这样,咱们项目装备就完成了。

装备Unity

导入工程成功后,咱们就要对 unity 进行代码装备运用。

首要,咱们创立一个叫UnityManager类的单例东西,专门来处理 unity 装备信息,以及交互运用。

咱们优先导入头文件#include <UnityFramework/NativeCallProxy.h>,装备一下信息参数,如下:

 @property (nonatomic, assign) int gArgc;
 @property (nonatomic, assign) char** gArgv;
 @property (nonatomic, strong) UnityFramework *unityFramework; 
 @property (nonatomic, strong, readonly ) UIView *unityView; 

然后咱们在 main 赋值一下gArgcgArgv

int main(int argc, char * argv[]) {
    @autoreleasepool {
        UnityManager.shareInstance.gArgc = argc;
        UnityManager.shareInstance.gArgv = argv;
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

接着咱们在 UnityManager 增加一个初始化 Unity 加载办法。

- (UnityFramework *)loadUnityFramework {
    NSString* bundlePath = nil;
    bundlePath = [[NSBundle mainBundle] bundlePath];
    bundlePath = [bundlePath stringByAppendingString: @"/Frameworks/UnityFramework.framework"];
    NSBundle* bundle = [NSBundle bundleWithPath: bundlePath];
    if ([bundle isLoaded] == false) {
        [bundle load];
    }
    UnityFramework* ufw = [bundle.principalClass getInstance];
    if (![ufw appController]) {
        // unity is not initialized
        [ufw setExecuteHeader: &_mh_execute_header];
    }
    return ufw;
}

然后咱们增加一个启动引擎的办法。

- (void)loadUnityWithComplete:(void(^)(void))complete {
    if (!self.unityView) {
        [self setUnityFramework: [self loadUnityFramework]];
        [[self unityFramework] setDataBundleId: "com.unity3d.framework"];
        [[self unityFramework] registerFrameworkListener: self];
        // 用于桥接运用
        [NSClassFromString(@"FrameworkLibAPI") registerAPIforNativeCalls:self];
        [[self unityFramework] runEmbeddedWithArgc:self.gArgc argv: self.gArgv appLaunchOpts: self.launchOptions];
        self.unityView = [[[self unityFramework] appController] rootView];
        self.unityFramework.appController.window.hidden = YES;
    }
    // 等unity回调信息用到
    self.loadUnityComplete = complete;
}

最终咱们把 unity 参加到咱们想要展示的视图傍边即可。

[self.view addSubview:OPRUnityManager.shareInstance.unityView];

视图的巨细能够自定义哦。

unity 协议对接

NativeCallProxy.h文件里边包含了咱们获取 unity 信息的桥接协议。

iOS嵌入虚拟引擎unity3d

所以咱们的 UnityManager 需求恪守NativeCallsProtocol。这样咱们就能够接收到 unity 的信息。

别的咱们侧重关注UnityFramework,这儿面赋予了咱们许多能够已运用功用。

iOS嵌入虚拟引擎unity3d

那这儿咱们想发消息给 unity,就能够使用下面的办法,姓名和 unity 一起命令即可。

- (void)sendMessageToGOWithName:(const char*)goName functionName:(const char*)name message:(const char*)msg
{
    UnitySendMessage(goName, name, msg);
}

就这样,咱们就完成了双方的通讯功用了。

unity遇到的问题

问题1:unityFramework 的 rootView 问题

咱们获取unityView视图是经过 unityFramework 的 rootView获取的,它本身就有自己的 window。

self.unityView = [[[self unityFramework] appController] rootView];

假如咱们一个A视图增加了 unityView ,然后去到别的一个B视图,也增加一个 unityView,这时分之前的A视图就不会有unityView,咱们只能回到A视图的时分,再重新布局一次。

问题2:unityView 手势问题

首要咱们得保证,unityView这个视图层级,没有被其它view挡住,就算这个view设置了clearColor,也会影响unityView的接触手势问题。

第二种就是滑动,刚好咱们unityView参加到咱们的 scrollView 里边,这时分左右接触 unityView,也会影响咱们 scrollView 的抖动。

这时分咱们需求参加一个手势判断,经过45度来决议,现在触发的是 unityView 的事件,还是 scrollView 的手势事件。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if ([gestureRecognizer isKindOfClass:UIPanGestureRecognizer.class]) {
        UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
        CGPoint translation = [pan translationInView:self];
        CGFloat absX = fabs(translation.x);
        CGFloat absY = fabs(translation.y);
        if (absX > absY ) {
            return NO;
        } else if (absY > absX) {
            return YES;
        }
    }
    return YES;
}

问题3:unity 排查问题

iOS 和 unity 交互方面,假如仅仅供给一个入口,那里边的排查处理就比较简单,那假如许多页面都可能用到unity,并且又有原生的,那交互起来就比较多了,假如出了问题,那该怎么排查呢?

  1. 首要 unity 尽量写全一些日志信息,这样咱们能够经过 Xcode 的控制台去看有没有报错信息。
  2. 咱们能够让 unity 敞开本地服务器,在咱们交互协议的时分,把信息发送出去,这样只需有手机就能够看调用流程。

问题4:unity 内存暴增问题

unity 引擎加进来,自然会增加内存,并且要渲染各种资源,绘制各种东西,这时分假如想排查为什么会增量许多,就能够经过 xcode -> Debug -> Capture GPU Workload 来查看内存问题。

iOS嵌入虚拟引擎unity3d

问题5: 电量耗费问题

自从引入了 unity 引擎后,电量耗费也明显加快了,这块的问题也是 unity 团队十分关注的问题。严峻的时分,电量耗费方面,GPU占用 45%。

咱们 APP 端能做了,就是及时给数据进行反馈。所以咱们在每个页面都给一个数据反馈,显现实时 CPU 运用率,内存巨细。

内存巨细:

+ (int64_t)memoryUsage {
    int64_t memoryUsageInByte = 0;
        task_vm_info_data_t vmInfo;
        mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
        kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
        if(kernelReturn == KERN_SUCCESS) {
            memoryUsageInByte = (int64_t) vmInfo.phys_footprint;
        } else {
        }
        return memoryUsageInByte;
}

CPU 运用率:

+ (double)getCpuUsage {
    kern_return_t           kr;
    thread_array_t          threadList;         
    mach_msg_type_number_t  threadCount;        
    thread_info_data_t      threadInfo;         
    mach_msg_type_number_t  threadInfoCount;    
    thread_basic_info_t     threadBasicInfo;   
    kr = task_threads(mach_task_self(), &threadList, &threadCount);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    double cpuUsage = 0;
    for (int i = 0; i < threadCount; i++) {
        threadInfoCount = THREAD_INFO_MAX;
        kr = thread_info(threadList[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount);
        if (kr != KERN_SUCCESS) {
            return -1;
        }
        threadBasicInfo = (thread_basic_info_t)threadInfo;
        if (!(threadBasicInfo->flags & TH_FLAGS_IDLE)) {
            cpuUsage += threadBasicInfo->cpu_usage;
        }
    }
    // 收回内存,避免内存泄漏
    vm_deallocate(mach_task_self(), (vm_offset_t)threadList, threadCount * sizeof(thread_t));
    return cpuUsage / (double)TH_USAGE_SCALE * 100.0;
}

最终

说实话,一路走来也遇到各种各样的坑,许多时分拿出来的计划也纷歧定是最优计划,后面会在写一篇关于 unity3d 联调之间产生的有趣事情。秉着一起学习的心态,也期望有专业的同学能提出更好的意见,万分感谢!!!

参考

iOS开发入门-unity手册