前言
- 最近在實驗室做了一個項目,用到了藍牙通訊和U3D的交互,都有很多坑,如:IOS與Unity3D界面之間的跳轉、數據的傳遞等操作。過程其實不是很難,只是比較繁瑣,剛開始可能一頭霧水,慢慢學習了解后,就會發現其實很簡單。
- 藍牙的一些知識在這里:IOS藍牙通訊詳解。
- 整篇文章主要介紹以下兩點
- IOS與Unity之間的界面切換
- IOS與Unity之間的函數調用以及傳值
IOS與Unity之間的界面切換
在IOS和Unity3D跨平臺開發中,基本就是兩個平臺之間的數據交互和界面切換。而界面交互是重中之重。利用Unity3D可以實現一些IOS原生界面所不具有的效果,而IOS原生界面則在整個程序界面中又顯得更和諧一些。所以無論是功能還是美觀,兩者之間的界面交互都很常用。
前期Unity準備
我們要用Unity和IOS的界面切換和數據交互,就必然要先實現一個Unity程序。我就默認讀者都會一些Unity基礎和OC技術吧。
-
首先建立一個新的Unity程序,在場景中拖入一個Cube。我們可以利用這個cube來展示Unity和IOS之間的數據交互。
一個簡單的Unity場景 - 我們在Camera下建立邦定一個腳本,在腳本中提供各類接口。
- 我們編輯腳本,給出一些基本的接口。
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工程,點擊File->Build Settings->IOS,(在build之前,我們需要填寫一些ID信息。其實這類信息也可以不現在填,在導出工程后,可以在Xcode里面填寫)然后點擊build。
從IOS界面切換到Unity界面
- 從IOS界面切換到Unity界面,需要做的是攔截Unity界面的啟動,當我們需要加載Unity界面的時候才讓其啟動。而要攔截Unity界面,就需要先搞明白Unity界面加載的順序。
main.mm
文件是整個Unity工程的入口。在main函數中先進行一些初始化和注冊操作,然后執行UIApplicationMain(argc, argv, nil, [NSString stringWithUTF8String:AppControllerClassName])
,這一句就執行了UnityAppController
對象。在UnityAppController
中,Unity整個程序就算是啟動起來了。所以我們需要了解UnityAppController
對象中一些方法的執行順序以便于我們攔截Unity界面。在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:
方法。而這個方法一旦執行,Unity界面就啟動起來了。所以我們需要做的攔截操作就在startUnity:
之前,即- (void)applicationDidBecomeActive:(UIApplication*)application
方法中調用IOS原生界面。 - 觀察一下
- (void)applicationDidBecomeActive:(UIApplication*)application
方法,中間有一句[self performSelector:@selector(startUnity:) withObject:application afterDelay:0];
,我們要做的就是把startUnity替換成我們自己的方法,在自己的方法中調用IOSUI,然后在UI中寫一些邏輯來讓IOS可以跳轉到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:@"跳轉到Unity界面" forState:UIControlStateNormal];
[btn addTarget:self action:@selector(startUnity:) forControlEvents:UIControlEventTouchUpInside];
[vc.view addSubview:btn];
[_window addSubview:vc.view];
}
- 運行結果,中間IOS界面運行的時候,我點擊了白色的按鈕跳轉。
從Unity界面跳轉到IOS界面
在Unity腳本中,我們已經添加了一個按鈕,并且給了接口。所以我們可以利用這個按鈕實現跳轉到IOS界面。
在IOS工程中調用Unity函數的方式是利用C語言接口。在IOS工程中利用形如
extern "C"
{
void functionName(parameter){
// do something
}
}
這樣的形式來調用Unity中的函數。
因為Unity界面跳轉到IOS界面涉及到了暫停Unity所以我們需要實現一個單例來判斷Unity的暫停或啟動
// 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()
來獲取。因為要返回之前的界面,所以我需要聲明一個強引用變量來引用之前的view。在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與Unity之間的函數調用以及傳值
在上一步中,其實我們已經實現了IOS和Unity函數調用和一部分傳值。現在更詳細的說明一下。
Unity調用IOS函數并進行傳值
- Unity調用IOS函數其實就是先在Unity腳本中聲明一個函數接口,然后在IOS程序中實現。其中
[DllImport("__Internal")]
private static extern void functionName (ParameterType Parameter);
為固定格式。這一句表明會在外部引用一個叫functionName (ParameterType Parameter)的函數,參數為Parameter。在IOS程序中實現上一步中已經講過,不再贅述。
IOS調用Unity函數并進行傳值
IOS調用Unity函數需要用到UnitySendMessage方法,方法中有三個參數
UnitySendMessage("gameobject", "Method",msg);
向unity發送消息
參數一為unity腳本掛載的gameobject
參數二為unity腳本中要調用的方法名
參數三為傳遞的數據,*注意:傳遞的數據只能是char 類型
我們利用在Unity工程中已經寫好的接口來試試數據的傳遞。
- 首先在我們需要添加2個按鈕,按鈕需要實現點擊后,傳遞數據過去,使視野中的cube(正方體)向左向右旋轉。
- 我們只需要在原生的Unity界面中添加按鈕。在原生界面中添加和在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);
}
- 在添加完成按鈕后,我們向攝像機發送消息。因為我們的腳本是綁定在主攝像頭上的,所以第一個參數為主攝像機的名字,第二個參數為腳本中的方法,第三個參數為數據。
-
因為我個人粗心,在Unity腳本中,關于旋轉的加減號忘記修改了,導致點擊左旋按鈕后物體也是右旋。只需要把腳本中
turnLeft
函數的-號改成+號就行了。運行如下:
總結
- IOS切換到Unity的界面切換主要通過攔截Unity界面的運行,在我們想要使用Unity界面的時候才讓Unity界面的方法繼續執行。IOS界面切換到Unity界面,如果Unity界面不是第一次執行,記得執行UnityPause(false)方法,因為我們在切換到IOS的時候,會暫停Unity界面。
- Unity切換到IOS界面將
viewController
更改成自已定義的界面控制器就可以了。切換到IOS界面的時候記得執行UnityPause(true)
方法。 - IOS傳遞數據到Unity界面主要通過UnitySendMessage()方法,*參數一為unity腳本掛載的gameobject,參數二為unity腳本中要調用的方法名,第三個參數為數據,數據格式只能為char **,利用這個方法也可以調用Unity的函數。
- Unity傳遞數據到IOS也是通過聲明外部函數,通過在Unity腳本調用外部函數的時候以參數的方式傳遞數據。
- 聲明外部函數的格式為:
// 聲明外部函數
[DllImport("__Internal")]
private static extern void functionName (ParameterType Parameter);
- IOS實現Unity中聲明的函數格式為:
extern "C"
{
void functionName(parameter){
// do something
}
}
結語
- 最近咨詢IOS和Unity通訊的人也有點多,寫出這個博客給大家作為參考。
- 跨平臺通訊都是自己在琢磨,錯誤難免,敬請指正。
- 有疑問可以給我發郵件,但是在發送郵件前,請保證您深思過此問題。若問題沒有意義或者在博客中有的話,我不會進行回復,請諒解。郵箱:lyk82465@gmail.com
- 最后附上Demo,點擊下載。Unity和IOS交互Demo