我正在参加「启航计划」
前言
最近虚拟引擎还是很火的,QQ超级秀,淘宝人生,抖音仔仔,玩的都是虚拟偶像,那假如咱们 APP 假如也想做相似的功用,那咱们好做吗,有没有什么优缺点,用什么计划比较好,这些都值得咱们去评论一下。由于没有接触过 UE4 ,本文仅评论 unity 计划怎么嵌入运用,怎么协议交互,以及带来的问题。
Unity导入iOS工程
其实 unity 导出的包,也是一个 Target,那咱们这儿选用的计划是把 Target 接入到咱们项目工程,详细看业务决议,有些是用iOS SDK 嵌入到 unity 的 iOS 包,这个不在本文评论范围内。本来是一个 Target ,咱们工程也是一个 Target ,这时分咱们就能够经过 workspace 来增加到一起。
首要咱们把 unity 包放到咱们工程下面。
然后,咱们在项目工程中增加 Unity-iPhone.xcodepro
。
把 unity 工程导入到项目中。
咱们还需求更改一些项目装备。
首要,咱们需求对 unity 工程的 bitcode 设置为 NO。 然后 Data 文件夹勾上 UnityFramework 。
最终,咱们需求把 NativeCallProxy.h
文件更改unityFramework
权限为Public
。
这样,咱们项目装备就完成了。
装备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 赋值一下gArgc
,gArgv
。
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 信息的桥接协议。
所以咱们的 UnityManager 需求恪守NativeCallsProtocol
。这样咱们就能够接收到 unity 的信息。
别的咱们侧重关注UnityFramework
,这儿面赋予了咱们许多能够已运用功用。
那这儿咱们想发消息给 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,并且又有原生的,那交互起来就比较多了,假如出了问题,那该怎么排查呢?
- 首要 unity 尽量写全一些日志信息,这样咱们能够经过 Xcode 的控制台去看有没有报错信息。
- 咱们能够让 unity 敞开本地服务器,在咱们交互协议的时分,把信息发送出去,这样只需有手机就能够看调用流程。
问题4:unity 内存暴增问题
unity 引擎加进来,自然会增加内存,并且要渲染各种资源,绘制各种东西,这时分假如想排查为什么会增量许多,就能够经过 xcode -> Debug -> Capture GPU Workload 来查看内存问题。
问题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手册