iOS嵌入虚拟引擎unity3d
我正在参加「掘金·启航计划」
前言
最近虚拟引擎还是很火的,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 联调之间产生的有趣事情。秉着一起学习的心态,也希望有专业的同学能提出更好的意见,万分感谢!!!
参考
- Bugly最新接入
- 我 与 unity 之间有趣的事情
- iOS嵌入虚拟引擎unity3d
- OC项目用Swift开发方便吗?
- Block的原理分析
- iOS多线程之三:NSThread,NSOperation,GCD超详细总结
- UITableViewCell加载图片优化
- iOS底层之isa指针分析,calloc分析和内存对齐(2)
- 详细分析iOS启动页广告
- 六大设计原则:迪米特法则
- 六大设计原则:接口隔离原则
- 六大设计原则:依赖倒置原则
- 六大设计原则:里氏替换原则
- 六大设计原则:开闭原则
- 六大设计原则:单一职责原则
- 光速打脸,Guideline 5.1.2又中招
- framework not found "BDPCAID"
- Xcode13.0和iOS15.0适配
- swift Core Data 简单使用
- 聊一下Swift的访问权限