Unity与iOS交互便捷指南

发表于2018-11-20
评论7 8k浏览
早年间在Unity中接入平台相关的功能,如SDK,IAP,IAB,PUSH,WebView等等需要和原生组件交互的时候,就要应用到AndroidJavaClass和DllImport,后来的SDK在这方面做得越来越好,几乎
不需要开发去手动的额外编写这部分的内容,但最近项目中几处需求有变,又需要用到这些

之前笔记总结得比较散乱,时间久了,也淡忘了,所以在这里列出来 ,作为可随时查阅的指南

目前时间有限,先提供Unity与iOS交互的指南,稍后再将Android补充上来


一、IOS篇

在Unity当中,要调用iOS原生的代码,需要通过特性Attribute,[DllImport(__Internal")]

 下面是Microsoft Docs定义:

[System.AttributeUsage(System.AttributeTargets.Method, Inherited=false)] [System.Runtime.InteropServices.ComVisible(true)] public sealed class DllImportAttribute : Attribute

只可以应用在方法上,指示该属性化方法由非托管动态链接库 (DLL) 作为静态入口点公开。

这是官方的解释,换句话说,应用了DllImport特性的方法,是由非拖管的第三方平台的语言来实现

比如我现在要在Unity端写一个按钮,点击按钮后,调出iOS原生的Alert弹框,具体流程如下:

1.在Unity中新建一个脚本xxx.cs,并定义Alert弹框方法,比如:
private static extern void ShowAlertMessage (string str);
这个方法必须是static extern进行修饰

因为要由非拖管的第三方平台来实现,所以我们加上特性Attribute
[DllImport("__Internal")]
    private static extern void ShowAlertMessage (string str);

(注:需要引入命名空间 using System.Runtime.InteropServices;)

2.我们做一个简单的按钮来直接的调用它。
if (GUILayout.Button ("showAlert")) 
            #if UNITY_IOS
            ShowAlertMessage ("The Presuit Of Happiness");
            #endif
        

这里要使用UNITY_IOS宏来进行平台的区分,相应的还有UNITY_ANDROID,我们直接传入string即可。

3.配置好Build Settings,导出Xcode工程,在导出的Xcode里,我们可以在Xcode工程结构的Classes或是Libraries下新建特定的类,并实现ShowAlertMessage方法。

这里我新建的类叫iOSWrapper,一般也命名为xxxImpl.h/mm

4.首先新建iOSWrapper.h 头文件 

#import <Foundation/Foundation.h>


@interface iOSWrapper : NSObject

    NSString* UnityGameObject;

    //...


@property(nonatomic,retainNSString* UnityGameObject;


+ (iOSWrapper *)sharedInstance;


-(void) ShowAlertMessage:(NSString*) text;



这里我定义了UnityGameObject,可以忽略它,这是以前遗留的代码,主要是在使用UnitySendMessage时,用来定义GameObject的值,如果要声明其它OC的对象,也需要像这样来定义。(有太久太久的时间没有接触OC开发,这里有些生疏了)

-(void) ShowAlertMessage:(NSString*) text;

这是一个iOSWrapper类的成员方法,用于实现弹窗的创建和显示。

+ (iOSWrapper *)sharedInstance;

这是一个单例,你也可以动态的创建,我只是习惯将他们常驻内存以便随时使用。

5.实现iOSWrapper.h 头文件,我们需要创建一个iOSWrapper.m类,并引入头文件 

#import "iOSWrapper.h"


static iOSWrapper * instance;


@implementation iOSWrapper


@synthesize UnityGameObject;


+(iOSWrapper*) sharedInstance

    if(instance==nil)

    {

        instance = [[iOSWrapper alloc] init];

        

    }

    return instance;


-(void) showAlertMessage:(NSString*) text{

    

    NSString* msg = [NSString stringWithFormat:text

                     ,nil];

    

    

    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@""

                                                        message:msg

                                                       delegate:nil

                                              cancelButtonTitle:NSLocalizedString(@"OK", @"")

                                              otherButtonTitles:nil];

    

    

    [alertView show];

//...

@end


在iOSWrapper类中,定义类的实例instance,并实现sharedInstance方法来创建单例。

showAlertMessage方法我们通过UIAlertView来实现弹出对话框。

(注:8.0以后,使用UIAlertController实现,UIAlertView被废弃,我这里目标平台是7.0,依然可以使用)

另外,Unity端传递过来的字符串都是char数组,我们需要将它转换为OC的NSString,同样的,当我们需要从iOS传递字符串给Unity时,也需要将NSString转换为char数组,在Unity的官方中有封装了两个方法,后面会提供上来。

6.这一步,我们需要实现C#中[DllImport(__Internal")]应用的方法在原生中的实现。

这里*.m文件是默认支持OC和C代码的,如果我们需要支持C++,则需要将*.m修改为*.mm,并修改文件的Type为Objective-C++ Source.

我们也需要将[DllImport("__Internal")]应用的方法定义在extern "C"下,extern "C"是告诉编译器这些方法要以C的函数命名规则来编译和链接。

为何要使用"extern C"?

因为C++是支持多态的,比如函数重载,所以C++编译器中对函数的命名都是以方法名+参数类似的形式,比如void foo(int x,int y),C++编译器会产生foo_int_int之类的名字,而C语言不支持多态,不支持函数重载,命名规则就是函数名,那么,如果我不加以限定,默认以C++的函数命名规则时,在C++中调用C函数时,就会出现类似无法找到在obj模块中C函数定义,UnsatisfiedLinkError:xxx之类的异常。
 
通常只要不是通过C++来调用,extern "C"都不是必须要加的。


extern "C" 定义在在iOSWrapper.mm类的外部,即:
@implementation iOSWrapper
//...
@end
的下面

extern "C"

    

    void ShowAlertMessage(const char* text)

    {

        NSLog(@"ShowAlertMessage:%s",text);

        NSString* _text = [[NSString alloc] initWithUTF8String:text];

        //

        [[iOSWrapper sharedInstance] showAlertMessage:text];

        

    }


上面有提到过,Unity传递过来的字符串是char数组,我们需要将他转换成NSString
NSString* _text = [[NSString allocinitWithUTF8String:text];

在Unity官方的例子中对此做了封装,可以拿过来直接 使用

C风格字符串转换NSString

// Converts C style string to NSString

NSString* CreateNSString (const char* string)

if (string)

return [NSString stringWithUTF8String: string];

else

return [NSString stringWithUTF8String: ""];


相应的,下面是NSString字符串转换为C风格字符串

// Helper method to create C string copy

char* MakeStringCopy (const char* string)

if (string == NULL)

return NULL;

char* res = (char*)malloc(strlen(string) + 1);

strcpy(res, string);

return res;


这两个方法要定义到extern "C"的上面,因为C语言是面向过程的,如果定义在extern "C"的下面,会提示找不到函数。

字符串是Unity与平台之间交互最频繁的操作。

这样,在Unity端打开一个iOS原生弹窗的功能就实现了,此时要将编写完成的iOSWrapper.h和iOSWrapper.mm文件 ,拷贝到Unity->Plugins->IOS下,这样生成xcode工程时,就会拷贝到Xcode的Libraries目录下。

现在实现了Unity端调用iOS端原生的组件API(窗口),我如何从原生iOS端传递数据给Unity?

这里就要使用到UnitySendMessage

void    UnitySendMessage(const char* obj, const char* method, const char* msg);


三个参数:对象名,方法名,参数

这里的对象名在我们当前Scene中Hierarchy下某个Obj,method方法名是Obj下绑定的xx脚本中的method,msg即是我们从平台端传递回来的参数,UnitySendMessage是iOS和Android通用的。

比如就以上面弹框为例,我们需要将弹框的内容再返回到Unity端,只需要这样:

UnitySendMessage("Main","ReceiveMessage",text);


Main就是我当前活动场景里的Obj,ReceiveMessage我们需要在xx脚本中进行定义

public void ReceiveMessage(string str)
    
        label = str;
    

(注:需要是非static方法)

这样就OK了,如果我要返回一个NSString的字符中,一定要通过MakeStringCopy转换成C风格的字符串转换才可以。(MakeStringCopy函数在上面有定义)

还有,字符串拼接,可以使用NSString的format

NSString* str = [NSString stringWithFormat:@"%s%s%s","aaa","bbb","ccc"];

return MakeStringCopy([str UTF8String]);

        


或者由纯C实现:

char a[] = "hello";

        char b[] = "world";

        char c[] = "cheetah";

        long len = _strlen(a)+_strlen(b)+_strlen(c);

        char* res = (char*)malloc(len);

        sprintf(res,"%s+%s+%s",a,b,c);


这里的_strlen是一个封装好的函数

long _strlen(const char* string)

    if(string)

    {

        return strlen(string)+1;

    }else

        return 1;

    }

因为C风格字符串默认最后一位是'\0',占一位,所以长度要+1

如果在Unity端想要直接返回string,在xx脚本定义如下:
[DllImport("__Internal")]
    private static extern string getCString ();

    [DllImport("__Internal")]
    private static extern string getNSString ();

在iOSWrapper.mm的extern "C"下实现:

const char* getCString()

    {

        return MakeStringCopy("this is a C type String");

    }

    

    const char* getNSString()

    {

        NSString* str = [NSString stringWithFormat:@"%s%s%s","JUST","DO","IT"];

        return MakeStringCopy([str UTF8String]);

    }



大概就这么多,下面是演示demo的下载地址(包含Unity官方的demo)

https://share.weiyun.com/5eQ6wAR

感谢您的阅读,如文中有误,欢迎指正,共同提高~













如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引