本片总结Unity3D和IOS混合开发。 Unity3D和IOS混合开发和Android也一样,有两个方式:
- 以Unity3D为主导混合开发
- 以IOS为主导混合开发
1.Unity3D为主导,调用Ios的方法。
由于U3D无法直接调用Objc或者Swift语言声明的接口,幸好U3D的主要语言是C#,因此可以利用C#的特性来访问C语言所定义的接口,然后再通过C接口再调用ObjC的代码(对于Swift代码则还需要使用OC桥接)
(1)用Xcode新建test.m和test.h两个文件,内容如下
// test.h
extern "C"
{
extern void outputAppendString (char *str1, char *str2);
}
/// test.m
#import <Foundation/Foundation.h>
void outputAppendString (char *str1, char *str2)
{
NSString *string1 = [[NSString alloc] initWithUTF8String:str1];
NSString *string2 = [[NSString alloc] initWithUTF8String:str2];
NSLog(@"###%@", [NSString stringWithFormat:@"%@ %@", string1, string2]);
}
IOS回调Unity中的方法有两种,如下
- UnitySendMessage方法
//str1:Unity中挂载脚本对象的名称
//str2:调用的方法名
//resultStr.UTF8String:传递的参数
UnitySendMessage("str1", "str2", resultStr.UTF8String);
- 非托管方法方式
Unity脚本中建立一个delegate声明,并使用UnmanagedFunctionPointer特性来标识该delegate是非托管方法,其中的CallingConvention.Cdel为调用时转换为C声明接口。
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ResultHandler(string resultString);
然后声明一个静态方法,并使用MonoPInvokeCallback特性来标记为回调方法,目的是让iOS中调用该方法时可以转换为对应的托管方法。
[MonoPInvokeCallback(typeof(ResultHandler))]
static void resultHandler (string resultStr)
{
}
注意:MonoPInvokeCallback特性参数是上一步中定义的非托管delegate。方法的声明一定要与delegate定义一致,并且必须为static进行修饰(iOS不支持非静态方法回调),否则会导致异常。
IOS这边Xcode文件test.m文件中定义一个接口,在C中需要定义一个与C#的delgate相同的函数指针ResultHandler。然后新增的outputAppendString2方法中多了一个回调参数resultHandler。这样就能够把C#传入的方法进行调用了。
typedef void (*ResultHandler) (const char *object);
void output(char *str1,char *str2,ResultHandler resultHandler){
resultHandler(参数)
}
UnitySendMessage方式 | 非托管方法方式 |
---|---|
接口声明固定,只能是void method(string message) | 接口灵活,可以为任意接口 |
不能带有返回值 | 可以带返回值 |
必须要挂载到对象后才能调用 | 可以不用挂载对象,但需要通过接口传入该调用方法 |
(2)将上面的这两个文件放到Unity3D工程Assets下,分别选择这两个文件,设置平台 (3)Unity3D新建一个脚本,实现下面内容
using System.Runtime.InteropServices;
//引入声明
[DllImport("__Internal")]
static extern void outputAppendString (string str1, string str2);
void Start () {
#if UNITY_IPHONE
outputAppendString("test1", "test2");
#endif
}
注意:对于指定平台的方法,一定要使用预处理指令#if来包括起来。否则在其他平台下面执行会导致异常。
对于上面第二步中采用非托管方法进行交互,Unity 实现方式区别就是要获取指针。
//引入申明
[DllImport("__Internal")]
static extern void output (string str1, string str2, IntPtr resultHandler);
ResultHandler handler = new ResultHandler(resultHandler);
//使用Marshal的GetFunctionPointerForDelegate来获取resultHandler的指针。
IntPtr fp = Marshal.GetFunctionPointerForDelegate(handler);
output ("Hello", "World", fp);
关于Marshal
Marshal类型主要是用于将C#中托管和非托管类型进行一个转换的桥梁。其提供了一系列的方法,这些方法包括用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法等。 本质上U3D与iOS的交互过程就是C#与C的交互过程,所以Marshal就成了交互的关键,因为C#与C的交互正正涉及到托管与非托管代码的转换。下面将举例说明,如何将一个C#的引用类型转换到对应的OC类型。 注意:Marshal申请的内存不是自动回收的,因此调用后需要通过显示方法FreeHGlobal调用释放。
Marshal.FreeHGlobal(存储的内容);
(4)unity3D 打包导出Ios项目,检查导出文件中是否有之前创建的test.m和test.h两个文件,然后编译。
2.以IOS为主导,添加Unity3D模块混合开发方式
(1)分别用Unity和Xcode创建两个项目,Unity3D打包IOS的工程包; (2)将Unity3D导出的IOS包中的Classes文件夹、Data文件夹、Libraries、MapFileParser、MapFileParser.sh这五个复制到Xcode创建的IOS工程中。 将Classes、Libraries以以下方式添加 将Data、QCAR(QCAR文件在Data->Raw里面)文件夹以以下方式添加 打开从unity中导出的iOS工程,查看其引用的库文件,添加到Xcode的工程中。 (3)在build settings中关闭bitcode。 在 other Linker Flags 添加-weak_framework CoreMotion -weak-lSystem 在Other C Flags中添加 -DINIT_SCRIPTING_BACKEND=1 添加pch文件
*** 注意: 如果项目中有多个pch文件,请将其合并成一个pch文件,再添加* 在Header Search Paths 添加头文件引用(路径自己打)**
(4)打开从unity导出的工程,查看Header Search Paths中添加的头文件,在Library Search Path 中添加库引用 打开从unity导出的工程,查看ibrary Search Path中添加的头文件,将其添加到自己工程的ibrary Search Path中 打开从unity导出的工程, 在build settings中查看user-Defined,并将其添加到自己项目的 user-Defined中 打开从unity导出的工程, 在build phases中查看Run Script (5)更改main.m文件为main.mm文件。将Classes中的main.mm中的内容复制,粘贴到原来工程的main.mm中。然后删除Classes中的main文件。并将main函数中的 UIApplicationMain方法修改为
UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
(6)在Classes的UnityAppController.mm中的 (void)applicationDidBecomeActive:(UIApplication*)application方法中, 注释掉[self performSelector:@selector(startUnity:) withObject: application afterDelay: 0]方法,这样做的目的,是为防止unity的内容随着应用的启动,而启动;
- (void)applicationDidBecomeActive:(UIApplication*)application
{
::printf("-> applicationDidBecomeActive()\n");
[self removeSnapshotView];
if (_unityAppReady)
{
if (UnityIsPaused() && _wasPausedExternal == false)
{
UnityWillResume();
UnityPause(0);
}
UnitySetPlayerFocus(1);
}
else if (!_startUnityScheduled)
{
_startUnityScheduled = true;
// [self performSelector: @selector(startUnity:) withObject: application afterDelay: 0];
}
_didResignActive = false;
}
(7)修改appDelegate.h
#import <UIKit/UIKit.h>
@class UnityAppController;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property(strong,nonatomic)UINavigationController *navi;
@property (strong, nonatomic) UIWindow *window;
@property (strong,nonatomic)UnityAppController* unityAppController;
- (void)shouldAttachRenderDelegate;
@end
(8)将appDelegate.m修改为appDelegate.mm,并对其内容进行修改
#import "AppDelegate.h"
#import "ViewController.h"
#import "UnityAppController.h"
@interface AppDelegate ()
@end
extern "C" void VuforiaSetGraphicsDevice(void* device, int deviceType, int eventType);
extern "C" void VuforiaRenderEvent(int marker);
@implementation AppDelegate
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
BOOL returnBool;
if (_unityAppController == nil) {
_unityAppController = [[UnityAppController alloc] init];
}
[_unityAppController application:application didFinishLaunchingWithOptions:launchOptions];
ViewController *vc = [[UIStoryboard storyboardWithName:@"Main" bundle:nil]instantiateViewControllerWithIdentifier:@"viewVC"];
self.navi = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController=self.navi;
[self.window makeKeyAndVisible];
return YES;
}
(void)applicationWillResignActive:(UIApplication *)application {
[_unityAppController applicationWillResignActive:application];
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
(void)applicationDidEnterBackground:(UIApplication *)application {
[_unityAppController applicationDidEnterBackground:application];
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
(void)applicationWillEnterForeground:(UIApplication *)application {
[_unityAppController applicationWillEnterForeground:application];
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
(void)applicationDidBecomeActive:(UIApplication *)application {
[_unityAppController applicationDidBecomeActive:application];
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
(void)applicationWillTerminate:(UIApplication *)application {
[_unityAppController applicationWillTerminate:application];
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
(void)shouldAttachRenderDelegate
{
UnityRegisterRenderingPlugin(&VuforiaSetGraphicsDevice, &VuforiaRenderEvent);
}
@end
(9)在Classes的UnityAppController.h中,更改GetAppController()方法(当然要先引入#import "AppDelegate.h")
inline UnityAppController* GetAppController()
{
AppDelegate *dele = (AppDelegate *)[UIApplication sharedApplication].delegate;
return (UnityAppController *)dele.unityAppController;
}
注意:如果有错,可以用下面方法代替
NS_INLINE UnityAppController* GetAppController() { AppDelegate *dele = (AppDelegate *)[UIApplication sharedApplication].delegate; return (UnityAppController *)dele.unityAppController; }
在UnityAppController.mm中重写- (void)shouldAttachRenderDelegate方法
(void)shouldAttachRenderDelegate
{
AppDelegate *deleg = [UIApplication sharedApplication].delegate;
[deleg shouldAttachRenderDelegate];
}
**上面的步骤就是把Unity的功能导入了IOS工程中,下面就是引用(调)Unity的功能: ** (10)用Xcode创建一个ViewController.m,导入
#import "UnityAppController.h"
#import "AppDelegate.h"
启动Unity 实现
[GetAppController() preStartUnity];
[GetAppController() startUnity:[UIApplication sharedApplication]];
[UnityGetMainWindow() makeKeyAndVisible];
在UnityAppController.h中申明-(void) restartUnity,在UnityAppController.mm中实现
(void) restartUnity
{
_window.rootViewController=_rootController;
[_window makeKeyAndVisible];
[UnityGetMainWindow() makeKeyAndVisible];
if (_didResignActive) {
UnityPause(false);
_didResignActive=NO;
}
}
这就启动Unity模块了,但是注意关闭或隐藏unity模块,也需要代码操作
在UnityAppController.mm中的- (void)startUnity:(UIApplication*)application方法里,添加实现方法
(void)doHideenUnity
{
UnityPause(true);
_didResignActive=YES;
Profiler_UninitProfiler();
AppDelegate *delet=[UIApplication sharedApplication].delegate;
[delet.window makeKeyAndVisible];
}
打包这就完成了IOS添加Unity模块的开发。
总结:需要注意的点
- 重复的main.mm,记得删除Classes文件的main.mm
- 如果有多个pch文件,记得进行合并
- 记得改AppDelegate.mm
- Privacy - Camera Usage Description App需要你的同意,才能访问摄像头
- -weak_framework CoreMotion -weak-lSystem横着写在一个格子里