前言
- 最近在实验室做了一个项目,用到了蓝牙通讯和U3D的交互,都有很多坑,如:IOS与Unity3D界面之间的跳转,数据的传递等操作过程其实不是很难,只是比较繁琐,刚开始可能一头雾水,慢慢学习了解后,就会发现其实很简单。
- 蓝牙的一些知识在这里:IOS蓝牙通讯详解。
- 整篇文章主要介绍以下两点
- IOS与团结之间的界面切换
- IOS与团结之间的函数调用以及传值
IOS与团结之间的界面切换
在IOS和Unity3D跨平台开发中,基本就是两个平台之间的数据交互和界面切换,而界面交互是重中之重。利用Unity3D可以实现一些IOS原生界面所不具有的效果,而IOS原生界面则在整个程序界面中又显得更和谐一些。所以无论是功能还是美观,两者之间的界面交互都很常用。
前期统一准备
我们要用统一和IOS的界面切换和数据交互,就必然要先实现一个统一程序。我就默认读者都会一些统一基础和OC技术吧。
-
首先建立一个新的统一程序,在场景中拖入一个多维数据集。我们可以利用这个立方体来展示团结和IOS之间的数据交互。
我们在摄像头下建立邦定一个脚本,在脚本中提供各类接口。
- 我们编辑脚本,给出一些基本的接口。
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class Test : MonoBehaviour {
public GameObject cube;
// DllImport这个方法相当于是告诉Unity,有一个unityToIOS函数在外部会实现。
// 使用这个方法必须要导入System.Runtime.InteropServices;
[DllImport("__Internal")]
private static extern void unityToIOS (string str);
void OnGUI()
{
// 当点击按钮后,调用外部方法
if (GUI.Button (new Rect (100, 100, 100, 30), "跳转到IOS界面")) {
// Unity调用ios函数,同时传递数据
unityToIOS ("Hello IOS");
}
}
// 向右转函数接口
void turnRight(string num){
float f;
if (float.TryParse (num, out f)) {// 将string转换为float,IOS传递数据只能用以string类型
Vector3 r = new Vector3 (cube.transform.rotation.x, cube.transform.rotation.y - 10f, cube.transform.rotation.z);
cube.transform.Rotate (r);
}
}
// 向左转函数接口
void turnLeft(string num){
float f;
if (float.TryParse (num, out f)) {// 将string转换为float,IOS传递数据只能用以string类型
Vector3 r = new Vector3 (cube.transform.rotation.x, cube.transform.rotation.y - 10f, cube.transform.rotation.z);
cube.transform.Rotate (r);
}
}
}
- 在给出接口之后,我们将Unity工程导出到IOS工程,点击文件 - >构建设置 - > IOS,(在构建之前,我们需要填写一些ID信息。其实这类信息也可以不现在填,在导出工程后,可以在Xcode的里面填写)然后点击建立。
从IOS界面切换到统一界面
- 从IOS界面切换到统一界面,需要做的是拦截统一界面的启动,当我们需要加载统一界面的时候才让其启动。而要拦截统一界面,就需要先搞明白统一界面加载的顺序。
main.mm
文件是整个统一工程的入口。在主函数中先进行一些初始化和注册操作,然后执行UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String:AppControllerClassName])
,这星级典句就执行了UnityAppController
对象。在UnityAppController
中,统一整个程序就算是启动起来了。所以我们需要了解UnityAppController
对象中一些方法的执行顺序以便于我们拦截统一界面。在UnityAppController
这个对象中,一些核心的方法已经有打印,如果想要知道所有方法的执行顺序,我们可以在每一个方法里面添加打印方法,此处为了简单,我就利用已经有的打印函数。
const char* AppControllerClassName = "UnityAppController";
int main(int argc, char* argv[])
{
@autoreleasepool
{
UnityInitTrampoline();
UnityParseCommandLine(argc, argv);
RegisterMonoModules();
NSLog(@"-> registered mono modules %p\n", &constsection);
RegisterFeatures();
// iOS terminates open sockets when an application enters background mode.
// The next write to any of such socket causes SIGPIPE signal being raised,
// even if the request has been done from scripting side. This disables the
// signal and allows Mono to throw a proper C# exception.
std::signal(SIGPIPE, SIG_IGN);
UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String:AppControllerClassName]);
}
return 0;
}
- 运行项目后,我们看到打印
- 在
- (void)applicationDidBecomeActive:(UIApplication*)application
这个方法中,了执行startUnity:
方法。而这个方法一旦执行,统一界面就启动起来了。所以需要我们做的拦截操作就在startUnity:
之前,即- (void)applicationDidBecomeActive:(UIApplication*)application
方法中调用IOS原生界面。 - 一下观察
- (void)applicationDidBecomeActive:(UIApplication*)application
方法,中间有一句[self performSelector:@selector(startUnity:) withObject:application afterDelay:0];
,我们要做的就是把startUnity替换成我们自己的方法,在自己的方法中调用IOSUI,然后在UI中写一些逻辑来让IOS可以跳转到统一界面。
- (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(startSelfIOSView) withObject:application afterDelay:0];
}
_didResignActive = false;
}
- (void)startSelfIOSView
{
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor blueColor];
vc.view.frame = [UIScreen mainScreen].bounds;
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 70, 30)];
btn.backgroundColor = [UIColor whiteColor];
[btn setTitle:@"跳转到Unity界面" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(startUnity:) forControlEvents:UIControlEventTouchUpInside];
[vc.view addSubview:btn];
[_window addSubview:vc.view];
}
- 运行结果,中间IOS界面运行的时候,我点击了白色的按钮跳转。
从统一界面跳转到IOS界面
在统一脚本中,我们已经添加了一个按钮,并且给了接口。所以我们可以利用这个按钮实现跳转到IOS界面。
在IOS工程中调用统一函数的方式是利用Ç语言接口。在IOS工程中利用形如
extern "C"
{
void functionName(parameter){
// do something
}
}
这样的形式来调用统一中的函数。
因为统一界面跳转到IOS界面涉及到了暂停团结所以我们需要实现一个单例来判断统一的暂停或启动
// LARManager.m
// Unity-iPhone
//
// Created by 柳钰柯 on 2016/12/15.
//
//
#import "LARManager.h"
@implementation LARManager
+ (instancetype)sharedInstance
{
static LARManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc] init];
});
return manager;
}
- (instancetype)init
{
if (self = [super init]) {
self.unityIsPaused = NO;
NSLog(@"单例初始化成功");
}
return self;
}
@end
- 跳转到IOS界面,我们需要实现在Unity脚本中声明的
unityToIOS
函数。值得注意的是:在extern“C”中,不能用OC的self
和self.window
获取到appController
和window
,必须使用UnityAppController
对象提供的方法GetAppController()
和UnityGetGLView()
来获取。因为要返回之前的界面,所以我需要声明一个强引用变量来引用之前的图。在UnityAppController.h
中声明@property (strong, nonatomic) UIViewController *vc
,在然后的刚才startSelfIOSView
中添加星级典句self.vc = vc;
。
实现单例之后的startSelfIOSView和unityToIOS函数
- (void)startSelfIOSView
{
// 单例变量unity没有暂停,设置为no
[LARManager sharedInstance].unityIsPaused = NO;
// IOS原生界面
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor blueColor];
vc.view.frame = [UIScreen mainScreen].bounds;
// 添加按钮
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 100, 200, 30)];
btn.backgroundColor = [UIColor whiteColor];
btn.titleLabel.backgroundColor = [UIColor blackColor];
[btn setTitle:@"跳转到Unity界面" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(startUnity:) forControlEvents:UIControlEventTouchUpInside];
// 在界面上添加按钮
[vc.view addSubview:btn];
self.vc = vc;
NSLog(@"设置界面为IOS界面");
self.window.rootViewController = vc;
}
extern "C"
{
// 对Unity中的unityToIOS方法进行实现
void unityToIOS(char* str){
// Unity传递过来的参数
NSLog(@"%s",str);
UnityPause(true);
// 跳转到IOS界面,Unity界面暂停
[LARManager sharedInstance].unityIsPaused = YES;
// GetAppController()获取appController,相当于self
// UnityGetGLView()获取UnityView,相当于_window
// 点击按钮后跳转到IOS界面,设置界面为IOS界面
GetAppController().window.rootViewController = GetAppController().vc;
}
}
- 实现效果:
IOS与团结之间的函数调用以及传值
在上一步中,其实我们已经实现了IOS和统一函数调用和一部分传值。现在更详细的说明一下。
统一调用IOS函数并进行传值
- 统一调用IOS函数其实就是先在统一脚本中声明一个函数接口,然后在IOS程序中实现。其中
[DllImport("__Internal")]
private static extern void functionName (ParameterType Parameter);
为固定格式。这一句表明会在外部引用一个叫functionName(ParameterType参数)的函数,参数为参数。在IOS程序中实现上一步中已经讲过,不再赘述。
IOS调用团结函数并进行传值
IOS调用团结函数需要用到UnitySendMessage方法,方法中有三个参数
UnitySendMessage(“gameobject”,“Method”,msg);
向统一发送消息
参数一为统一脚本挂载的gameobject
参数二为unity脚本中要调用的方法名
参数三为传递的数据,* 注意:传递的数据只能是字符类型**
我们利用在统一工程中已经写好的接口来试试数据的传递。
- 首先在我们需要添加2个按钮,按钮需要实现点击后,传递数据过去,使视野中的立方体(正方体)向左向右旋转。
- 我们只需要在原生的统一界面中添加按钮。在原生界面中添加和在IOS工程中添加是差不多的。在界面还没有显示的时候,添加进去即可,我在
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
这状语从句:方法中添加按钮。代码如下:
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
/*省略了一些Unity的操作*/
/*.....*/
[self createUI];
[self preStartUnity];
// 添加右旋按钮
UIButton *rightBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, 150, 100, 30)];
rightBtn.backgroundColor = [UIColor whiteColor];
[rightBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[rightBtn setTitle:@"向右旋转" forState:UIControlStateNormal];
[rightBtn addTarget:self action:@selector(turnRight) forControlEvents:UIControlEventTouchUpInside];
self.rightBtn = rightBtn;
// 添加左旋按钮
UIButton *leftBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, 200, 100, 30)];
leftBtn.backgroundColor = [UIColor whiteColor];
[leftBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[leftBtn setTitle:@"向左旋转" forState:UIControlStateNormal];
[leftBtn addTarget:self action:@selector(turnLeft) forControlEvents:UIControlEventTouchUpInside];
self.leftBtn = leftBtn;
// 在Unity界面添加按钮
[UnityGetGLViewController().view addSubview:_rightBtn];
[UnityGetGLViewController().view addSubview:_leftBtn];
// if you wont use keyboard you may comment it out at save some memory
[KeyboardDelegate Initialize];
return YES;
}
// 左旋按钮响应事件
- (void)turnRight
{
const char* str = [[NSString stringWithFormat:@"10"] UTF8String];
UnitySendMessage("Main Camera", "turnRight", str);
}
// 右旋按钮响应事件
- (void)turnLeft
{
const char* str = [[NSString stringWithFormat:@"10"] UTF8String];
UnitySendMessage("Main Camera", "turnLeft", str);
}
- 在添加完成按钮后,我们向摄像机发送消息。因为我们的脚本是绑定在主摄像头上的,所以第一个参数为主摄像机的名字,第二个参数为脚本中的方法,第三个参数为数据。
- 因为我个人粗心,在统一脚本中,关于旋转的加减号忘记修改了,导致点击左旋按钮后物体也是右旋只需要把脚本中。
turnLeft
函数的-号改成+就号行了运行如下:
总结
- IOS切换到Unity的界面切换主要通过拦截Unity界面的运行,在我们想要使用Unity界面的时候才让Unity 界面切换到Unity界面,如果Unity界面不是第一次执行,记得执行UnityPause(假)方法,因为我们在切换到IOS的时候,会暂停统一界面。
- 统一切换到IOS将界面
viewController
更改自已分类中翻译定义的界面控制器就可以了。切换到IOS的界面时候记得执行UnityPause(true)
方法。 - IOS传递数据到统一界面主要通过UnitySendMessage()方法,* 参数一为统一脚本挂载的游戏对象,参数二为统一脚本中要调用的方法名,第三个参数为数据,数据格式只能为char **,利用这个方法也可以调用Unity的函数。
- 统一传递数据到IOS也是通过声明外部函数,通过在统一脚本调用外部函数的时候以参数的方式传递数据。
- 声明外部函数的格式为:
// 声明外部函数
[DllImport("__Internal")]
private static extern void functionName (ParameterType Parameter);
- IOS实现统一中声明的函数格式为:
extern "C"
{
void functionName(parameter){
// do something
}
}
结语
- 最近咨询IOS和统一通讯的人也有点多,写出这个博客给大家作为参考。
- 跨平台通讯都是自己在琢磨,错误难免,敬请指正。
- 有疑问可以给我发邮件,但是在发送邮件前,请保证您的深思过此问题。若问题没有意义或者在博客中有的话,我不会进行回复,请谅解。邮箱:[email protected]
- 最后附上Demo,点击下载.Unity 和IOS交互演示
作者:Larrycal
链接:https://www.jianshu.com/p/4c49655aff8b
来源:
著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。