iOS4U3D插件开发
目标
实现Unity中调用iOS原生方法,并传递参数;实现iOS方法中调用Unity3D中的方法,并传递参数,本文主要教会大家开发方式与流程,具体需要在iOS原生代码下执行哪些功能,因人而异,可自由发挥。
实现方法
鉴于iOS开发可使用oc或swift语言,同时支持c与c++代码混合开发,各位看官用什么语言请自行变通、不必过于死板,本文以oc为例。
网上大多的方法是创建.mm文件并编写相应的oc与c++的代码,实现相关功能后将文件放入Unity3D中的Assets/Plugins/iOS文件夹下,然后编写对应的c#类文件引用.mm文件中的方法,从而完成互相调用;
理解过程及交互方式后,本文打算从Unity3D方向,也就是从c#代码到oc代码的方式来讲解这一插件开发过程;
首先在Unity3D中创建相应的c#脚本,定义你将使用到的功能函数,只要按特定方式定义即可,这就要求大家对自己要开发的功能明确清晰,此处以打开相册功能举例:
[DllImport ("__Internal")] private static extern void _iosOpenPhotoLibrary (string unityGameObject, string unityMethodName); [DllImport ("__Internal")] private static extern void _iosOpenPhotoAlbums (string unityGameObject, string unityMethodName); void unityMethodName(string fileName) { // Do something.}// 这里的带有[DllImport ("__Internal")]标记的方法将对应到oc中的方法 // 同时注意,方法声明需要添加extern前缀,我们的目的是调用iOS的方法打开相册选择图片 // 同时,考虑到完成这一过程后,我们需要向Unity3D发送一个通知来调用相关的方法,而这个回调的方法为: UnitySendMessage(unityGameObject, unityMethodName, 传递的参数); // 因而我们在Unity3D中调用oc的方法的时候传递了对象名及方法名的参数给它,以此来实现动态的调用不同的对象上的不同方法 // 注意这个方法的调用是不区分脚本名称的,只要是该对象上的脚本中存在该方法,就会被调用到,这里我们将被oc回调的函数直接创建在同一个脚本文件中;编写完c#的脚本,现在让我们来编写oc的代码,你可以将Unity3D的工程导出成Xcode的工程,然后进行开发,或者直接新建一个Xcode的工程,然后进行oc代码的开发;
两种方法的不同点在于,第一种直接就可以边开发边调试了,而后者需要将实现好的功能代码拷贝到Unity3D中后再导出工程才可看具体效果,我们直接采用前者;
将Unity3D工程导出成为Xcode工程,在Unity3D工程中,iOS的代码文件是放在Assets/Plugins/iOS目录下的,导出后可在Xcode工程中的Libraries/Plugins/iOS目录下找到,因为我们是先导出工程在编写oc代码的,所以我们直接在Xcode对应的目录下创建oc代码文件,接下来我们就来编写下oc代码:
// 头文件:IosPhotoHelper.h @interface IosPhotoHelper : UIViewController<UIImagePickerControllerDelegate,UINavigationControllerDelegate> @end // 实现文件:IosPhotoHelper.mm #import "IosPhotoHelper.h" @implementation IosPhotoHelper char _unityGameObject[30] = "Controller"; char _unityMethodName[30] = "unityMethodName"; - (void)showPicker:(UIImagePickerControllerSourceType)type allowsEditing:(BOOL)flag { UIImagePickerController *picker = [[UIImagePickerController alloc] init]; picker.delegate = self; picker.sourceType = type; picker.allowsEditing = flag; [self presentViewController:picker animated:YES completion:nil]; } - (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary*)info { UIImage *rawImage; // 获取图像 if (picker.allowsEditing) { rawImage = [info objectForKey:UIImagePickerControllerEditedImage]; } else { rawImage = [info objectForKey:UIImagePickerControllerOriginalImage]; } // 缩放图像 UIGraphicsBeginImageContext(CGSizeMake(256,256)); [rawImage drawInRect:CGRectMake(0, 0, 256, 256)]; UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); // 获取图像数据 NSData *imgData = UIImagePNGRepresentation(scaledImage); if(imgData == nil) { imgData= UIImageJPEGRepresentation(rawImage, 1.0); } // 保存图像到沙盒 NSString *photoName = @"imageios.png"; NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:photoName]; [imgData writeToFile:path atomically:YES]; // 关闭相册并通知Unity根据图像名称去获取图像 [picker dismissViewControllerAnimated:YES completion:^{ UnitySendMessage(_unityGameObject, _unityMethodName, photoName.UTF8String); }]; [self dismissViewControllerAnimated:YES completion:nil]; } - (void)imagePickerControllerDidCancel:(UIImagePickerController*)picker { [picker dismissViewControllerAnimated:YES completion:^{ UnitySendMessage(_unityGameObject, _unityMethodName, "Cancel"); }]; [self dismissViewControllerAnimated:YES completion:nil]; } @end #if defined (__cplusplus) extern "C" { #endif void _iosOpenPhotoLibrary(char *unityGameObject, char *unityMethodName) { strcpy(_unityGameObject, unityGameObject); strcpy(_unityMethodName, unityMethodName); if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) { IosPhotoHelper * app = [[IosPhotoHelper alloc] init]; [UnityGetGLViewController() presentViewController:app animated:YES completion:nil]; [app showPicker:UIImagePickerControllerSourceTypePhotoLibrary allowsEditing:NO]; } else { UnitySendMessage(_unityGameObject, _unityMethodName, "Cancel"); } } void _iosOpenPhotoAlbums(char *unityGameObject, char *unityMethodName) { strcpy(_unityGameObject, unityGameObject); trcpy(_unityMethodName, unityMethodName); if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) { IosPhotoHelper * app = [[IosPhotoHelper alloc] init]; [UnityGetGLViewController() presentViewController:app animated:YES completion:nil]; [app showPicker:UIImagePickerControllerSourceTypeSavedPhotosAlbum allowsEditing:NO]; } else { _iosOpenPhotoLibrary(_unityGameObject, _unityMethodName); } } #if defined (__cplusplus) } #endif代码编写完成后,别急着运行工程,否则你会发现有可能点击开启相册按钮就Crash了,原因很简单,需要添加相册使用权限,在Xcode工程中的info.plist文件中添加一个字符串字段:NSPhotoLibraryUsageDescription,内容自行填写,例如:需要使用相册来选择图片;
准备就绪,现在让我们运行Xcode工程进行测试,测试OK后,将对应的.h和.mm文件拷贝到Unity3D工程中的Assets/Plugins/iOS目录下,若Unity3D的功能还未开发完全,则可继续开发,后续编译后刚才的功能已经可以用了,当然,刚才导出并测试OK的Xcode工程也是可以直接发布app的;
注意,c#中的string传递到c++中的方法时,对应为指针类型的char,若需要与NSString之间转换,则调用NSString的一些UTF8的静态方法即可实现,在Xcode中用代码提示就可以看到相应方法了,各位自行尝试,另外,Unity3D中的Application.persistentDataPath目录对应的是iOS中的NSHomeDirectory()方法调用后返回的目录下的Documents目录,即:[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]目录,明确这点后,就可以实现在iOS存储好图片,然后返回文件名在Unity3D中再将该文件进行各种的操作的功能;
好了,Unity3D工程的iOS插件开发教程到此结束,若实际项目开发过程中有遇到问题,可细细斟酌以上小点,看看是哪里没注意到,结合Debug的日志打印,相信你能很快解决问题;