title: unity-与ios交互
categories: Unity3d
tags: [unity, ios]
date: 2021-01-31 14:16:54
comments: false
mathjax: true
toc: true
unity-与ios交互
unity 与 ios 交互
-
写 ios 插件, 丢到平台层目录 Assets/Plugins/iOS 下
-
头文件 iOSBridgePlugin.h
// ---------------------------------------- // --- ios 自定义插件接口声明 // ---------------------------------------- #ifdef __cplusplus extern "C"{ #endif void ShowTips(const char* goName, const char* callFnName, const char* msg); #ifdef __cplusplus } // extern "C" #endif
-
实现文件 iOSBridgePlugin.mm.
这个是混编文件 (oc + c/c++) , xcode 可以自动识别为 Objective-C++ 文件
#import "iOSBridgePlugin.h" #import "Classes/Unity/UnityInterface.h" // 引入 unity 相关 api void ShowTips(const char* goName, const char* callFnName, const char* msg) { NSLog(@"--- ShowTips"); NSString* go = [NSString stringWithUTF8String:goName]; // c 字符串 转成 oc 字符串, 这里一定要先转成 oc, 不然 const char* 调用后就会释放掉栈内存, 会导致 UnitySendMessage 回传 unity 失败 NSString* fn = [NSString stringWithUTF8String:callFnName]; NSString* content = [NSString stringWithUTF8String:msg]; UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Hi, wilker." message:content preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* yesButton = [UIAlertAction actionWithTitle:@"Reply" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { const char* rspMsg = [[NSString stringWithFormat: @"ios replay: %@", content] UTF8String]; // oc 字符串 转成 c 字符串 UnitySendMessage([go UTF8String], [fn UTF8String], rspMsg); // ios 调用 unity }]; [alert addAction:yesButton]; UIViewController* rootCtrl=[UIApplication sharedApplication].keyWindow.rootViewController; [rootCtrl presentViewController:alert animated:YES completion:nil]; }
-
-
在 csharp 代码导入并使用这个插件 api
GameMgr.cs
using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.UI; public class GameMgr : MonoBehaviour { public Text txt; public GameObject go; void Start() { gameObject.name = "GameMgr"; DontDestroyOnLoad(gameObject); } public void OnNativeCall(string data) { Debug.LogFormat("--- OnNativeCall, data: {0}", data); txt.text = data; } public void CallNative() { string msg = "hello ios-" + Random.Range(1000, 9999); #if UNITY_IPHONE || UNITY_IOS ShowTips(gameObject.name, "OnNativeCall", msg); // 调用 ios api #else Debug.LogErrorFormat("--- no implemention on platform: {0}", Application.platform.ToString()); #endif } void Update() { go.transform.Rotate(Vector3.up * 50 * Time.deltaTime); } // ------------------- ios native api #if UNITY_IPHONE || UNITY_IOS [DllImport("__Internal")] private static extern void ShowTips(string goName, string callFnName, string msg); #endif }
目录目录
-
build 一下生产 xcode 工程
-
打开 xcode.
Plugins 目录会移到 Libraries 目录下
-
cmd + R 编译并运行到手机.
不能运行到 ios 模拟器上, 因为 unity 导出的是 arm 架构的库, 而 ios 模拟器时 x86 架构.
-
效果
-
扩展 UnityAppController
- Unity iOS接SDK,定制UnityAppController - https://www.codeleading.com/article/76004167121/
Unity3d
提供了一套插件机制,可以很方便地在项目中使用自己的CustomAppController
继承并重写默认的UnityAppController
的方法。
在 Unity 插件目录下创建以下文件:
Assets/Plugins/iOS/CustomAppController.mm
文件名必须是 xxxxAppController.mm
,xxxx
前缀可自选,但不能省略,如CustomAppController.mm
;否则在 Build
项目的时候,会被移动到错误的目录中去。
// CustomAppController.mm
#import "UnityAppController.h"
@interface CustomAppController : UnityAppController
@end
IMPL_APP_CONTROLLER_SUBCLASS (CustomAppController)
@implementation CustomAppController
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions{
[super application:application didFinishLaunchingWithOptions:launchOptions];
NSLog(@"--- CustomAppController didFinishLaunchingWithOptions");
return YES;
}
@end
注意,上面的CustomAppController.mm
中有一个宏IMPL_APP_CONTROLLER_SUBCLASS
,Unity 就是通过 IMPL_APP_CONTROLLER_SUBCLASS
知道要使用我们定制的 CustomAppController
而不是使用默认的UnityAppController
。
unity view 初始化完后搞事情
Classes/UI/UnityAppController+ViewHandling.h
头文件中包含了 unity controller, view 等初始化的工作, 所以可以重写里面的部分方法去搞事情.
简单版
-
在 [扩展 UnityAppController](#扩展 UnityAppController) 的基础上, 重写 willStartWithViewController 方法即可, 这个方法在
Classes/UI/UnityAppController+ViewHandling.h
头文件中// CustomAppController.mm #import "Classes/UI/UnityAppController+ViewHandling.h" @implementation CustomAppController - (void)willStartWithViewController:(UIViewController *)controller{ [super willStartWithViewController:controller]; NSLog(@"--- iOSAppController.willStartWithViewController, controller: %@", controller); UILabel* lbl = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 300, 50)]; lbl.textColor = [UIColor blackColor]; lbl.text = @"hello world 2"; [controller.view addSubview:lbl]; } @end
复杂版 - 监听 UintyView 生命周期
应用场景: 想要在 view 加载前后搞事情. unity 提供了一个 delegate 让你去实现并注册进去, 它就会在 view 的生命周期里通知你.
-
自定义一个类 (如: MyPlugin) 实现 UnityViewControllerListener 的 delegate 接口
// MyPlugin.h #import "Classes/PluginBase/UnityViewControllerListener.h" #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface MyPlugin : NSObject<UnityViewControllerListener> @end NS_ASSUME_NONNULL_END // MyPlugin.m #import "MyPlugin.h" @implementation MyPlugin - (void)viewWillLayoutSubviews:(NSNotification*)notification { NSLog(@"--- MyPlugin.viewWillLayoutSubviews"); } - (void)viewDidLayoutSubviews:(NSNotification*)notification { NSLog(@"--- MyPlugin.viewDidLayoutSubviews"); } - (void)viewWillDisappear:(NSNotification*)notification { NSLog(@"--- MyPlugin.viewWillDisappear"); } - (void)viewDidDisappear:(NSNotification*)notification { NSLog(@"--- MyPlugin.viewDidDisappear"); } - (void)viewWillAppear:(NSNotification*)notification { NSLog(@"--- MyPlugin.viewWillAppear"); } // 这个可以作为 unity view 加载完成的标记. 只会调用一次 - (void)viewDidAppear:(NSNotification*)notification { NSLog(@"--- MyPlugin.viewDidAppear"); NSLog(@"--- viewDidAppear.obj: %@", notification.object); UILabel* lbl = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 300, 50)]; lbl.textColor = [UIColor blackColor]; lbl.text = @"hello world"; UIViewController* rootVC = [UIApplication sharedApplication].windows[0].rootViewController; [rootVC.view addSubview:lbl]; NSLog(@"--- rootVc: %@", rootVC); NSLog(@"--- rootVc.view: %@", rootVC.view); // 这个就是 unityView } @end
-
纳尼? import 头文件
UnityViewControllerListener.h
后编译报错:Unable to compile in XCode: Expected identifier or '('
,extern C
的几个 const 变量引发的.解决办法就是去改下源文件. 参考: https://answers.unity.com/questions/1251495/unable-to-compile-in-xcode-expected-identifier-or.html
-
修改 UnityViewControllerListener.h 内容
// 注释掉 extern 变量声明 //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewWillLayoutSubviews; //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewDidLayoutSubviews; //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewWillDisappear; //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewDidDisappear; //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewWillAppear; //extern "C" __attribute__((visibility("default"))) NSString* const kUnityViewDidAppear; //extern "C" __attribute__((visibility("default"))) NSString* const kUnityInterfaceWillChangeOrientation; //extern "C" __attribute__((visibility("default"))) NSString* const kUnityInterfaceDidChangeOrientation; // 新加 cosnt 变量 NSString* const kUnityViewWillLayoutSubviews = @"kUnityViewWillLayoutSubviews"; NSString* const kUnityViewDidLayoutSubviews = @"kUnityViewDidLayoutSubviews"; NSString* const kUnityViewDidDisappear = @"kUnityViewDidDisappear"; NSString* const kUnityViewDidAppear = @"kUnityViewDidAppear"; NSString* const kUnityViewWillDisappear = @"kUnityViewWillDisappear"; NSString* const kUnityViewWillAppear = @"kUnityViewWillAppear"; NSString* const kUnityInterfaceWillChangeOrientation = @"kUnityInterfaceWillChangeOrientation"; NSString* const kUnityInterfaceDidChangeOrientation = @"kUnityInterfaceDidChangeOrientation";
-
修改 UnityViewControllerListener.mm 内容
// 注释掉 extern 变量定义 //DEFINE_NOTIFICATION(kUnityViewWillLayoutSubviews); //DEFINE_NOTIFICATION(kUnityViewDidLayoutSubviews); //DEFINE_NOTIFICATION(kUnityViewWillDisappear); //DEFINE_NOTIFICATION(kUnityViewDidDisappear); //DEFINE_NOTIFICATION(kUnityViewWillAppear); //DEFINE_NOTIFICATION(kUnityViewDidAppear); //DEFINE_NOTIFICATION(kUnityInterfaceWillChangeOrientation); //DEFINE_NOTIFICATION(kUnityInterfaceDidChangeOrientation);
done. 编译就可以通过了. (感觉这种需要动到 内部源码 的事情就不靠谱, 可能有其他更好的方式)
-
-
-
在 [扩展 UnityAppController](#扩展 UnityAppController) 的基础上, 重写 UnityAppController 的
createUnityView
方法. 这个方法在Classes/UI/UnityAppController+ViewHandling.h
头文件中// CustomAppController.mm #import "Classes/PluginBase/UnityViewControllerListener.h" #import "Classes/UI/UnityAppController+ViewHandling.h" @interface CustomAppController : UnityAppController @property (strong, nonatomic) MyPlugin* plugin; @end @implementation CustomAppController - (UnityView *)createUnityView { NSLog(@"--- iOSAppController.createUnityView"); self.plugin = [MyPlugin new]; UnityRegisterViewControllerListener(plugin); // 注册监听 UnityView* unity_view = [super createUnityView]; NSLog(@"--- unity_view: %@", unity_view); return unity_view; } @end
-
done. 打印出的日志
2021-02-19 03:11:26.920198+0800 testunityios[45199:3842542] --- iOSAppController.createUnityView // 这个接口调用的比 didFinishLaunchingWithOptions 更早 2021-02-19 03:11:26.922306+0800 testunityios[45199:3842542] --- unity_view: <UnityView: 0x10645dc40; frame = (0 0; 375 812); layer = <CAMetalLayer: 0x281690800>> 2021-02-19 03:11:26.930272+0800 testunityios[45199:3842542] --- MyPlugin.viewWillAppear 2021-02-19 03:11:26.989400+0800 testunityios[45199:3842542] --- iOSAppController didFinishLaunchingWithOptions 2021-02-19 03:11:27.052447+0800 testunityios[45199:3842542] --- MyPlugin.viewWillLayoutSubviews 2021-02-19 03:11:27.095425+0800 testunityios[45199:3842542] --- MyPlugin.viewDidLayoutSubviews 2021-02-19 03:11:27.959829+0800 testunityios[45199:3842542] --- MyPlugin.viewWillAppear 2021-02-19 03:11:27.960434+0800 testunityios[45199:3842542] --- MyPlugin.viewWillDisappear 2021-02-19 03:11:27.960855+0800 testunityios[45199:3842542] --- MyPlugin.viewWillDisappear 2021-02-19 03:11:27.963680+0800 testunityios[45199:3842542] --- MyPlugin.viewWillAppear 2021-02-19 03:11:28.264654+0800 testunityios[45199:3842542] --- MyPlugin.viewWillLayoutSubviews 2021-02-19 03:11:28.264727+0800 testunityios[45199:3842542] --- MyPlugin.viewDidLayoutSubviews 2021-02-19 03:11:28.264775+0800 testunityios[45199:3842542] --- viewDidLayoutSubviews.obj: <UnityDefaultViewController: 0x10645f3f0> 2021-02-19 03:11:28.265655+0800 testunityios[45199:3842542] --- MyPlugin.viewDidDisappear 2021-02-19 03:11:28.266328+0800 testunityios[45199:3842542] --- MyPlugin.viewDidAppear // 这个可以作为 unity view 加载完成的标记. 只会调用一次 2021-02-19 03:11:28.266369+0800 testunityios[45199:3842542] --- viewDidAppear.obj: <UnityDefaultViewController: 0x10645f3f0> 2021-02-19 03:11:28.266905+0800 testunityios[45199:3842542] --- rootVc: <UnityDefaultViewController: 0x10645f3f0> 2021-02-19 03:11:28.267018+0800 testunityios[45199:3842542] --- rootVc.view: <UnityView: 0x10645dc40; frame = (0 0; 375 812); autoresize = W+H; layer = <CAMetalLayer: 0x281690800>> // 这个 根节点的 view 就是 unity_view, 地址一样 2021-02-19 03:11:28.267078+0800 testunityios[45199:3842542] --- MyPlugin.viewWillLayoutSubviews 2021-02-19 03:11:28.267116+0800 testunityios[45199:3842542] --- MyPlugin.viewDidLayoutSubviews
构建 xcode 工程的后处理
-
使用PostProcessBuild设定Unity产生的Xcode Project - https://www.cnblogs.com/pandawuwyj/p/6904770.html
-
Unity导出已配置好的Xcode工程 - https://www.jianshu.com/p/51d6b8371e0f
-
Unity iOS 插件开发与SDK接入 - https://www.yangzhenlin.com/unity-ios-plugin/
-
Embed framework to Xcode project - https://forum.unity.com/threads/embed-framework-to-xcode-project.389211/
string framework = Path.Combine(defaultLocationInProj, coreFrameworkName); string fileGuid = proj.AddFile(framework, "Frameworks/" + framework, PBXSourceTree.Sdk); PBXProjectExtensions.AddFileToEmbedFrameworks(proj, targetGuid, fileGuid);
开源后处理插件 XUPorter
- GitHub - https://github.com/onevcat/XUPorter
- blog - https://onevcat.com/2012/12/xuporter/
参数说明
- group:The group name in Xcode to which files and folders will added by this projmods file
- libs: Xcode Build Phases 里需要添加的 内置库, 如: libz.dylib. (如果想添加自己的 .a 库, 需要使用 files 添加)
- frameworks:Xcode Build Phases 里需要添加的 内置 framework, 如: Security.framework. (如果想添加自己的 .framework 库, 需要使用 files 添加)
- headerpaths:Header Search Paths in Build Setting of Xcode
- files:需要添加的文件. (路径为 .projmods 文件的相对路径)
- folders:Folders which should be added. All file and folders(recursively) will be added
- excludes:Regular expression pattern. Matched files will not be added.
- compiler_flags: Compiler flags which should be added, e.g. “-Wno-vexing-parse”
- linker_flags: Linker flags which should be added, e.g. “-ObjC”
- embed_binaries: 可选字段, 支持 XCode 6+
Embed Framework
特性. 添加的 framework 必须已经在frameworks
或files
字段里.- 如果是使用的第三方库是 framework 形式, 则需要用到这个字段, 路径和 files 一样是相对路径. 可以将 framework 设置为 embed 状态. 不然 运行时 会报找不到 库错误.
- !!! 貌似有 bug, 手动添加没问题, 用插件就有问题 !!! 所以这个字段还是不要用了, 自己打个补丁吧 !!!
- plist: edit the Info.plist file, only the urltype is supported currently. in url type settings,
name
andschemes
are required, whilerole
is optional and isEditor
by default.
踩坑
运行时报错 找不到库
错误: dyld: Library not loaded
找了网上的方法:在 Linked Frameworks and Libraries 中,把 framework 设为 Optional、或是修改Library search path,全是扯淡,完全不管用。
最后找到如下方法:
-
在 TARGETS -> General -> Frameworks, Libraries, and Embedded Content 中,点击,把第三方 framework embed 状态置为 embed & sign
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqQzEN7L-1613716436055)(http://yxbl.itengshe.com/20210214191820-1.webp)]
然后在 build phases 中就会显示出来 embed frameworks
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaqS2YiE-1613716436056)(http://yxbl.itengshe.com/20210214192541-1.webp)]
参考: https://www.jianshu.com/p/fe8486cb33f8