前言
- 最近在實(shí)驗(yàn)室做了一個(gè)項(xiàng)目,用到了藍(lán)牙通訊和U3D的交互小泉,都有很多坑膏孟,如:IOS與Unity3D界面之間的跳轉(zhuǎn)拌汇、數(shù)據(jù)的傳遞等操作噪舀。過(guò)程其實(shí)不是很難与倡,只是比較繁瑣,剛開(kāi)始可能一頭霧水息拜,慢慢學(xué)習(xí)了解后少欺,就會(huì)發(fā)現(xiàn)其實(shí)很簡(jiǎn)單赞别。
- 藍(lán)牙的一些知識(shí)在這里:IOS藍(lán)牙通訊詳解配乓。
- 整篇文章主要介紹以下兩點(diǎn)
- IOS與Unity之間的界面切換
- IOS與Unity之間的函數(shù)調(diào)用以及傳值
IOS與Unity之間的界面切換
在IOS和Unity3D跨平臺(tái)開(kāi)發(fā)中犹芹,基本就是兩個(gè)平臺(tái)之間的數(shù)據(jù)交互和界面切換腰埂。而界面交互是重中之重盐固。利用Unity3D可以實(shí)現(xiàn)一些IOS原生界面所不具有的效果,而IOS原生界面則在整個(gè)程序界面中又顯得更和諧一些志电。所以無(wú)論是功能還是美觀挑辆,兩者之間的界面交互都很常用鱼蝉。
前期Unity準(zhǔn)備
我們要用Unity和IOS的界面切換和數(shù)據(jù)交互箫荡,就必然要先實(shí)現(xiàn)一個(gè)Unity程序羔挡。我就默認(rèn)讀者都會(huì)一些Unity基礎(chǔ)和OC技術(shù)吧绞灼。
-
首先建立一個(gè)新的Unity程序低矮,在場(chǎng)景中拖入一個(gè)Cube军掂。我們可以利用這個(gè)cube來(lái)展示Unity和IOS之間的數(shù)據(jù)交互。
- 我們?cè)贑amera下建立邦定一個(gè)腳本肠虽,在腳本中提供各類(lèi)接口税课。
- 我們編輯腳本韩玩,給出一些基本的接口找颓。
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class Test : MonoBehaviour {
public GameObject cube;
// DllImport這個(gè)方法相當(dāng)于是告訴Unity击狮,有一個(gè)unityToIOS函數(shù)在外部會(huì)實(shí)現(xiàn)彪蓬。
// 使用這個(gè)方法必須要導(dǎo)入System.Runtime.InteropServices;
[DllImport("__Internal")]
private static extern void unityToIOS (string str);
void OnGUI()
{
// 當(dāng)點(diǎn)擊按鈕后档冬,調(diào)用外部方法
if (GUI.Button (new Rect (100, 100, 100, 30), "跳轉(zhuǎn)到IOS界面")) {
// Unity調(diào)用ios函數(shù),同時(shí)傳遞數(shù)據(jù)
unityToIOS ("Hello IOS");
}
}
// 向右轉(zhuǎn)函數(shù)接口
void turnRight(string num){
float f;
if (float.TryParse (num, out f)) {// 將string轉(zhuǎn)換為float披坏,IOS傳遞數(shù)據(jù)只能用以string類(lèi)型
Vector3 r = new Vector3 (cube.transform.rotation.x, cube.transform.rotation.y - 10f, cube.transform.rotation.z);
cube.transform.Rotate (r);
}
}
// 向左轉(zhuǎn)函數(shù)接口
void turnLeft(string num){
float f;
if (float.TryParse (num, out f)) {// 將string轉(zhuǎn)換為float棒拂,IOS傳遞數(shù)據(jù)只能用以string類(lèi)型
Vector3 r = new Vector3 (cube.transform.rotation.x, cube.transform.rotation.y - 10f, cube.transform.rotation.z);
cube.transform.Rotate (r);
}
}
}
- 在給出接口之后着茸,我們將Unity工程導(dǎo)出到IOS工程涮阔,點(diǎn)擊File->Build Settings->IOS敬特,(在build之前牺陶,我們需要填寫(xiě)一些ID信息掰伸。其實(shí)這類(lèi)信息也可以不現(xiàn)在填狮鸭,在導(dǎo)出工程后歧蕉,可以在Xcode里面填寫(xiě))然后點(diǎn)擊build。
從IOS界面切換到Unity界面
- 從IOS界面切換到Unity界面,需要做的是攔截Unity界面的啟動(dòng)锁蠕,當(dāng)我們需要加載Unity界面的時(shí)候才讓其啟動(dòng)匿沛。而要攔截Unity界面,就需要先搞明白Unity界面加載的順序。
main.mm
文件是整個(gè)Unity工程的入口抡笼。在main函數(shù)中先進(jìn)行一些初始化和注冊(cè)操作推姻,然后執(zhí)行UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String:AppControllerClassName])
藏古,這一句就執(zhí)行了UnityAppController
對(duì)象拧晕。在UnityAppController
中梅垄,Unity整個(gè)程序就算是啟動(dòng)起來(lái)了队丝。所以我們需要了解UnityAppController
對(duì)象中一些方法的執(zhí)行順序以便于我們攔截Unity界面机久。在UnityAppController
這個(gè)對(duì)象中膘盖,一些核心的方法已經(jīng)有打印衔憨,如果想要知道所有方法的執(zhí)行順序践图,我們可以在每一個(gè)方法里面添加打印方法,此處為了簡(jiǎn)單德崭,我就利用已經(jīng)有的打印函數(shù)眉厨。
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;
}
- 運(yùn)行項(xiàng)目后憾股,我們看到打印
- 在
- (void)applicationDidBecomeActive:(UIApplication*)application
這個(gè)方法中茴恰,執(zhí)行了startUnity:
方法往枣。而這個(gè)方法一旦執(zhí)行粉渠,Unity界面就啟動(dòng)起來(lái)了霸株。所以我們需要做的攔截操作就在startUnity:
之前淳衙,即- (void)applicationDidBecomeActive:(UIApplication*)application
方法中調(diào)用IOS原生界面箫攀。 - 觀察一下
- (void)applicationDidBecomeActive:(UIApplication*)application
方法,中間有一句[self performSelector:@selector(startUnity:) withObject:application afterDelay:0];
,我們要做的就是把startUnity替換成我們自己的方法肥印,在自己的方法中調(diào)用IOSUI绝葡,然后在UI中寫(xiě)一些邏輯來(lái)讓IOS可以跳轉(zhuǎn)到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(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:@"跳轉(zhuǎn)到Unity界面" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(startUnity:) forControlEvents:UIControlEventTouchUpInside];
[vc.view addSubview:btn];
[_window addSubview:vc.view];
}
- 運(yùn)行結(jié)果,中間IOS界面運(yùn)行的時(shí)候,我點(diǎn)擊了白色的按鈕跳轉(zhuǎn)。
從Unity界面跳轉(zhuǎn)到IOS界面
在Unity腳本中绞蹦,我們已經(jīng)添加了一個(gè)按鈕力奋,并且給了接口。所以我們可以利用這個(gè)按鈕實(shí)現(xiàn)跳轉(zhuǎn)到IOS界面幽七。
在IOS工程中調(diào)用Unity函數(shù)的方式是利用C語(yǔ)言接口景殷。在IOS工程中利用形如
extern "C"
{
void functionName(parameter){
// do something
}
}
這樣的形式來(lái)調(diào)用Unity中的函數(shù)。
因?yàn)閁nity界面跳轉(zhuǎn)到IOS界面涉及到了暫停Unity所以我們需要實(shí)現(xiàn)一個(gè)單例來(lái)判斷Unity的暫驮杪牛或啟動(dòng)
// 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
- 跳轉(zhuǎn)到IOS界面,我們需要實(shí)現(xiàn)在Unity腳本中聲明的
unityToIOS
函數(shù)挪蹭。值得注意的是:在extern "C"中亭饵,不能用OC的self
和self.window
獲取到appController
和window
,必須使用UnityAppController
對(duì)象提供的方法GetAppController()
和UnityGetGLView()
來(lái)獲取梁厉。因?yàn)橐祷刂暗慕缑妫晕倚枰暶饕粋€(gè)強(qiáng)引用變量來(lái)引用之前的view踏兜。在UnityAppController.h
中聲明@property (strong, nonatomic) UIViewController *vc
,然后在剛才的startSelfIOSView
中添加一句self.vc = vc;
词顾。
實(shí)現(xiàn)單例之后的startSelfIOSView和unityToIOS函數(shù)
- (void)startSelfIOSView
{
// 單例變量unity沒(méi)有暫停,設(shè)置為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:@"跳轉(zhuǎn)到Unity界面" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(startUnity:) forControlEvents:UIControlEventTouchUpInside];
// 在界面上添加按鈕
[vc.view addSubview:btn];
self.vc = vc;
NSLog(@"設(shè)置界面為IOS界面");
self.window.rootViewController = vc;
}
extern "C"
{
// 對(duì)Unity中的unityToIOS方法進(jìn)行實(shí)現(xiàn)
void unityToIOS(char* str){
// Unity傳遞過(guò)來(lái)的參數(shù)
NSLog(@"%s",str);
UnityPause(true);
// 跳轉(zhuǎn)到IOS界面碱妆,Unity界面暫停
[LARManager sharedInstance].unityIsPaused = YES;
// GetAppController()獲取appController肉盹,相當(dāng)于self
// UnityGetGLView()獲取UnityView,相當(dāng)于_window
// 點(diǎn)擊按鈕后跳轉(zhuǎn)到IOS界面疹尾,設(shè)置界面為IOS界面
GetAppController().window.rootViewController = GetAppController().vc;
}
}
- 實(shí)現(xiàn)效果:
IOS與Unity之間的函數(shù)調(diào)用以及傳值
在上一步中上忍,其實(shí)我們已經(jīng)實(shí)現(xiàn)了IOS和Unity函數(shù)調(diào)用和一部分傳值。現(xiàn)在更詳細(xì)的說(shuō)明一下纳本。
Unity調(diào)用IOS函數(shù)并進(jìn)行傳值
- Unity調(diào)用IOS函數(shù)其實(shí)就是先在Unity腳本中聲明一個(gè)函數(shù)接口窍蓝,然后在IOS程序中實(shí)現(xiàn)。其中
[DllImport("__Internal")]
private static extern void functionName (ParameterType Parameter);
為固定格式繁成。這一句表明會(huì)在外部引用一個(gè)叫functionName (ParameterType Parameter)的函數(shù)吓笙,參數(shù)為Parameter。在IOS程序中實(shí)現(xiàn)上一步中已經(jīng)講過(guò)巾腕,不再贅述面睛。
IOS調(diào)用Unity函數(shù)并進(jìn)行傳值
IOS調(diào)用Unity函數(shù)需要用到UnitySendMessage方法,方法中有三個(gè)參數(shù)
UnitySendMessage("gameobject", "Method",msg);
向unity發(fā)送消息
參數(shù)一為unity腳本掛載的gameobject
參數(shù)二為unity腳本中要調(diào)用的方法名
參數(shù)三為傳遞的數(shù)據(jù)尊搬,*注意:傳遞的數(shù)據(jù)只能是char 類(lèi)型
我們利用在Unity工程中已經(jīng)寫(xiě)好的接口來(lái)試試數(shù)據(jù)的傳遞叁鉴。
- 首先在我們需要添加2個(gè)按鈕,按鈕需要實(shí)現(xiàn)點(diǎn)擊后佛寿,傳遞數(shù)據(jù)過(guò)去幌墓,使視野中的cube(正方體)向左向右旋轉(zhuǎn)。
- 我們只需要在原生的Unity界面中添加按鈕。在原生界面中添加和在ios工程中添加是差不多的克锣。在界面還沒(méi)有顯示的時(shí)候茵肃,添加進(jìn)去即可,我在
- (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:@"向右旋轉(zhuǎn)" 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:@"向左旋轉(zhuǎn)" 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;
}
// 左旋按鈕響應(yīng)事件
- (void)turnRight
{
const char* str = [[NSString stringWithFormat:@"10"] UTF8String];
UnitySendMessage("Main Camera", "turnRight", str);
}
// 右旋按鈕響應(yīng)事件
- (void)turnLeft
{
const char* str = [[NSString stringWithFormat:@"10"] UTF8String];
UnitySendMessage("Main Camera", "turnLeft", str);
}
- 在添加完成按鈕后验残,我們向攝像機(jī)發(fā)送消息。因?yàn)槲覀兊哪_本是綁定在主攝像頭上的巾乳,所以第一個(gè)參數(shù)為主攝像機(jī)的名字您没,第二個(gè)參數(shù)為腳本中的方法,第三個(gè)參數(shù)為數(shù)據(jù)胆绊。
-
因?yàn)槲覀€(gè)人粗心氨鹏,在Unity腳本中,關(guān)于旋轉(zhuǎn)的加減號(hào)忘記修改了压状,導(dǎo)致點(diǎn)擊左旋按鈕后物體也是右旋仆抵。只需要把腳本中
turnLeft
函數(shù)的-號(hào)改成+號(hào)就行了。運(yùn)行如下:
總結(jié)
- IOS切換到Unity的界面切換主要通過(guò)攔截Unity界面的運(yùn)行种冬,在我們想要使用Unity界面的時(shí)候才讓Unity界面的方法繼續(xù)執(zhí)行镣丑。IOS界面切換到Unity界面,如果Unity界面不是第一次執(zhí)行娱两,記得執(zhí)行UnityPause(false)方法莺匠,因?yàn)槲覀冊(cè)谇袚Q到IOS的時(shí)候,會(huì)暫停Unity界面十兢。
- Unity切換到IOS界面將
viewController
更改成自已定義的界面控制器就可以了趣竣。切換到IOS界面的時(shí)候記得執(zhí)行UnityPause(true)
方法。 - IOS傳遞數(shù)據(jù)到Unity界面主要通過(guò)UnitySendMessage()方法旱物,*參數(shù)一為unity腳本掛載的gameobject遥缕,參數(shù)二為unity腳本中要調(diào)用的方法名,第三個(gè)參數(shù)為數(shù)據(jù),數(shù)據(jù)格式只能為char **异袄,利用這個(gè)方法也可以調(diào)用Unity的函數(shù)通砍。
- Unity傳遞數(shù)據(jù)到IOS也是通過(guò)聲明外部函數(shù),通過(guò)在Unity腳本調(diào)用外部函數(shù)的時(shí)候以參數(shù)的方式傳遞數(shù)據(jù)烤蜕。
- 聲明外部函數(shù)的格式為:
// 聲明外部函數(shù)
[DllImport("__Internal")]
private static extern void functionName (ParameterType Parameter);
- IOS實(shí)現(xiàn)Unity中聲明的函數(shù)格式為:
extern "C"
{
void functionName(parameter){
// do something
}
}
結(jié)語(yǔ)
- 最近咨詢(xún)IOS和Unity通訊的人也有點(diǎn)多封孙,寫(xiě)出這個(gè)博客給大家作為參考。
- 跨平臺(tái)通訊都是自己在琢磨讽营,錯(cuò)誤難免虎忌,敬請(qǐng)指正。
- 有疑問(wèn)可以給我發(fā)郵件橱鹏,但是在發(fā)送郵件前膜蠢,請(qǐng)保證您深思過(guò)此問(wèn)題堪藐。若問(wèn)題沒(méi)有意義或者在博客中有的話(huà),我不會(huì)進(jìn)行回復(fù)挑围,請(qǐng)諒解礁竞。郵箱:lyk82465@gmail.com
- 最后附上Demo,點(diǎn)擊下載杉辙。Unity和IOS交互Demo