1. 关于Unity3D

Unity3D(以下简称U3D)是由Unity Technologies开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

作为一款跨平台开发工具,难免会与原生平台进行一些交互操作来完成一些特定的平台功能。例如:你需要直接操作iOS的IAP来实现游戏中的内付费功能;甚至一些第三方SDK没有提供U3D版本的情况下,你会直接在原生系统平台调用其提供接口等等。

下面将为大家介绍,在U3D下如何实现与iOS系统的交互工作,来满足一些需要借助原生系统的功能需求。

2. From U3D to iOS

2.1 实现原理

由于U3D无法直接调用Objc或者Swift语言声明的接口,幸好U3D的主要语言是C#,因此可以利用C#的特性来访问C语言所定义的接口,然后再通过C接口再调用ObjC的代码(对于Swift代码则还需要使用OC桥接)。例如,有如下C语言方法:

  1. void nativeMethod ()
  2. {
  3. NSLog(@"------- objc method call...\n");
  4. }

在C#中则可以像下面代码一样进行引入和调用:

  1. using System.Runtime.InteropServices;
  2. [DllImport("__Internal")]
  3. internal extern static void nativeMethod();

其中DllImport为一个Attribute,目的是通过非托管方式将库中的方法导出到C#中进行使用。而传入"__Internal"则是表示这个是一个静态库或者是一个内部方法。通过上面的声明,这个方法就可以在C#里面进行调用了。如:

  1. public class Sample
  2. {
  3. public void test ()
  4. {
  5. nativeMethod();
  6. }
  7. }

2.2 实现步骤

下面通过一个拼接字符串的例子来说明怎么样从U3D中传入两个字符串到iOS中,然后由iOS拼接后通过NSLog输出结果:

  1. 首先新建test.mtest.h两个文件。分别写入如下内容:
  1. /// test.h
  2. extern "C"
  3. {
  4. extern void outputAppendString (char *str1, char *str2);
  5. }
  1. /// test.m
  2. #import <Foundation/Foundation.h>
  3. void outputAppendString (char *str1, char *str2)
  4. {
  5. NSString *string1 = [[NSString alloc] initWithUTF8String:str1];
  6. NSString *string2 = [[NSString alloc] initWithUTF8String:str2];
  7. NSLog(@"###%@", [NSString stringWithFormat:@"%@ %@", string1, string2]);
  8. }
  1. 然后将上面的两个文件放到U3D项目的Assets目录中。如图:
 
放入U3D项目
  1. 分别选择test.htest.m文件,在Inspector面板中去掉Any Platforms的勾选,然后保留iOS这一项选中。如图:
 
设置平台插件
  1. 新建一个叫Sample的C#脚本文件,并在这个文件中写入c接口的声明,如:
  1. public class Sample : MonoBehaviour
  2. {
  3. //引入声明
  4. [DllImport("__Internal")]
  5. static extern void outputAppendString (string str1, string str2);
  6. }
  1. 在Start方法中调用该方法,如:
  1. void Start ()
  2. {
  3. #if UNITY_IPHONE
  4. outputAppendString("Hello", "World");
  5. #endif
  6. }

注意:对于指定平台的方法,一定要使用预处理指令#if来包括起来。否则在其他平台下面执行会导致异常。

  1. 拖动Sample脚本到场景的Main Camera对象中,让脚本进行挂载。
 
挂载脚本
  1. 使用快捷键Command+Shift+B(或者点击菜单File -> Build Settings)调出Build Settings窗口,将项目导出为iOS项目。如图:
 
导出iOS项目
  1. 打开导出的iOS项目,先检查之前创建的test.mtest.h是否已经导出到项目中。如图:
 
检查文件
  1. 编译运行应用,可以看到控制台中会输出合并后的字符串信息,如:
  1. 2018-01-22 16:17:15.143166+0800 ProductName[29211:4392515] ###Hello World

3. From iOS to U3D

对于如何从iOS中调用U3D的接口,分为两种办法:一种是通过UnitySendMessage方法来调用Unity所定义的方法。另一种方法则是通过入口参数,传入一个U3D的非托管方法,然后调用该方法即可。两种方式的对比如下:

UnitySendMessage方式 非托管方法方式
接口声明固定,只能是void method(string message) 接口灵活,可以为任意接口。
不能带有返回值 可以带返回值
必须要挂载到对象后才能调用。 可以不用挂载对象,但需要通过接口传入该调用方法

下面将一一讲述两种方式的实现。

3.1 UnitySendMessage

  1. 基于上面调用iOS接口的例子,在Sample.cs中增加一个callback方法。如:
  1. void callback (string resultStr)
  2. {
  3. Debug.LogFormat ("result string = {0}", resultStr);
  4. }
  1. 由于项目已经挂载Sample.cs到Main Camera中,这就不用再进行挂载。然后打开test.m文件,在outputAppendString方法中调用callback方法,并将组合字符串返回给U3D。如:
  1. void outputAppendString (char *str1, char *str2)
  2. {
  3. NSString *string1 = [[NSString alloc] initWithUTF8String:str1];
  4. NSString *string2 = [[NSString alloc] initWithUTF8String:str2];
  5. NSString *resultStr = [NSString stringWithFormat:@"%@ %@", string1, string2];
  6. NSLog(@"###%@", resultStr);
  7. UnitySendMessage("Main Camera", "callback", resultStr.UTF8String);
  8. }
  1. 导出iOS项目,编译运行看执行结果。
  1. 2018-01-22 17:47:00.137259+0800 ProductName[29561:4429040] ###Hello World
  2. Setting up 1 worker threads for Enlighten.
  3. Thread -> id: 170cb3000 -> priority: 1
  4. result string = Hello World
  5. (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

3.2 非托管方法

  1. Sample.cs中建立一个delegate声明,并使用UnmanagedFunctionPointer特性来标识该delegate是非托管方法。代码如下:
  1. [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
  2. public delegate void ResultHandler(string resultString);

其中的CallingConvention.Cdel为调用时转换为C声明接口。

  1. 然后声明一个静态方法,并使用MonoPInvokeCallback特性来标记为回调方法,目的是让iOS中调用该方法时可以转换为对应的托管方法。如:
  1. [MonoPInvokeCallback(typeof(ResultHandler))]
  2. static void resultHandler (string resultStr)
  3. {
  4. }

注意:MonoPInvokeCallback特性参数是上一步中定义的非托管delegate。方法的声明一定要与delegate定义一致,并且必须为static进行修饰(iOS不支持非静态方法回调),否则会导致异常。

  1. 打开test.m文件,定义一个新的接口,如:
  1. typedef void (*ResultHandler) (const char *object);
  2. void outputAppendString2 (char *str1, char *str2, ResultHandler resultHandler)
  3. {
  4. NSString *string1 = [[NSString alloc] initWithUTF8String:str1];
  5. NSString *string2 = [[NSString alloc] initWithUTF8String:str2];
  6. NSString *resultStr = [NSString stringWithFormat:@"%@ %@", string1, string2];
  7. NSLog(@"###%@", resultStr);
  8. resultHandler (resultStr.UTF8String);
  9. }

上面代码可见,在C中需要定义一个与C#的delgate相同的函数指针ResultHandler。然后新增的outputAppendString2方法中多了一个回调参数resultHandler。这样就能够把C#传入的方法进行调用了。

  1. 回到Sample.cs文件,定义outputAppendString2的声明。
  1. [DllImport("__Internal")]
  2. static extern void outputAppendString2 (string str1, string str2, IntPtr resultHandler);

注意:回调方法的参数必须是IntPtr类型,表示一个函数指针。

  1. Start方法中调用outputAppendString2,并将回调方法转换为IntPtr类型传给方法。如:
  1. ResultHandler handler = new ResultHandler(resultHandler);
  2. IntPtr fp = Marshal.GetFunctionPointerForDelegate(handler);
  3. outputAppendString2 ("Hello", "World", fp);

上面代码使用MarshalGetFunctionPointerForDelegate来获取resultHandler的指针。

  1. 导出iOS项目,编译运行。
  1. 2018-01-22 19:02:31.339317+0800 ProductName[29852:4459349] ###Hello World
  2. result string = Hello World
  3. Sample:outputAppendString2(String, String, IntPtr)
  4. (Filename: /Users/builduser/buildslave/unity/build/artifacts/generated/common/runtime/DebugBindings.gen.cpp Line: 51)

4. 类型传递

对于基础类型数据(如:int、double、string等)是可以直接从U3D中传递给iOS的。具体对应关系如下表所示:

U3D iOS
short short
int int
long long long
bool bool
char char
string char *
struct struct
byte[] void *
IntPtr void *

注意

  • 引用型数据不能直接从U3D传给iOS。如果需要传递这样的类型,可以考虑将对象序列化成byte数组,然后在iOS中进行反序列化将其还原回来。
  • 对于string类型,会自动转换为c语言中的char *。但是由于C#中的string是托管类型,因此char *是无法直接转换为string的,所以不要直接在返回值中返回char *类型。下一节会针对返回值进行详细的说明。
  • struct类型数据中不能包含引用型数据,否则在调用接口时会报告类似下面的提示:
  1. MarshalDirectiveException: Cannot marshal field 't' of type 'TestStructType': Reference type field marshaling is not supported.

4.1 关于Marshal

Marshal类型主要是用于将C#中托管和非托管类型进行一个转换的桥梁。其提供了一系列的方法,这些方法包括用于分配非托管内存、复制非托管内存块、将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法等。

本质上U3D与iOS的交互过程就是C#与C的交互过程,所以Marshal就成了交互的关键,因为C#与C的交互正正涉及到托管与非托管代码的转换。下面将举例说明,如何将一个C#的引用类型转换到对应的OC类型。

  1. 首先在C#中声明一个类型Person
  1. class Person
  2. {
  3. public string name;
  4. public int age;
  5. }
  1. 在C中声明一个接口printPersonInfo用于打印传递过来的Person信息,如:
  1. void printPersonInfo(void *personData);
  1. 在C#中声明此接口
  1. [DllImport("__Internal")]
  2. static extern void printPersonInfo (IntPtr personData);
  1. 创建一个Person的实例,然后将其序列化成byte数组,这里使用到对象序列化的一些知识。
  1. Person person = new Person();
  2. person.name = "vimfung";
  3. person.age = 18;
  4. List<byte> buf = new List<byte>();
  5. //写入name
  6. byte[] bytes = BitConverter.GetBytes (person.name.Length);
  7. if (BitConverter.IsLittleEndian)
  8. {
  9. Array.Reverse (bytes);
  10. }
  11. buf.AddRange (bytes);
  12. buf.AddRange (Encoding.UTF8.GetBytes (person.name));
  13. //写入age
  14. bytes = BitConverter.GetBytes (person.age);
  15. if (BitConverter.IsLittleEndian)
  16. {
  17. Array.Reverse (bytes);
  18. }
  19. buf.AddRange(bytes);
  20. byte[] bufBytes = buf.ToArray();
  1. 将byte数组通过Marshal类转换为IntPtr类型,并传入给C接口。
  1. //转换成功IntPtr
  2. IntPtr personData = Marshal.AllocHGlobal(bufBytes.Length);
  3. Marshal.Copy(bufBytes, 0, personData, bufBytes.Length);
  4. printPersonInfo(personData);
  5. Marshal.FreeHGlobal(personData);

注意:Marshal申请的内存不是自动回收的,因此调用后需要通过显示方法FreeHGlobal调用释放。

  1. 回到C代码中,并实现其内部处理逻辑,如:
  1. void printPersonInfo(void *personData)
  2. {
  3. int offset = 0;
  4. //获取name
  5. int nameLen = (((unsigned char *)personData) [offset] << 24)
  6. | (((unsigned char *)personData) [offset + 1] << 16)
  7. | (((unsigned char *)personData) [offset + 2] << 8)
  8. | (((unsigned char *)personData) [offset + 3]);
  9. offset += 4;
  10. char *nameBuf = malloc(sizeof(char) * (nameLen + 1));
  11. memset(nameBuf, 0, nameLen);
  12. memcpy(nameBuf, (char *)personData + offset, nameLen);
  13. offset += nameLen;
  14. NSLog(@"person name = %s", nameBuf);
  15. //获取age
  16. int age = (((unsigned char *)personData) [offset] << 24)
  17. | (((unsigned char *)personData) [offset + 1] << 16)
  18. | (((unsigned char *)personData) [offset + 2] << 8)
  19. | (((unsigned char *)personData) [offset + 3]);
  20. NSLog(@"person age = %d", age);
  21. }
  1. 导出iOS项目,编译运行可以看到日志里面的输出结果
  1. 2018-01-29 14:38:56.378376+0800 ProductName[8584:1163121] person name = vimfung
  2. 2018-01-29 14:38:56.378509+0800 ProductName[8584:1163121] person age = 18

5. 返回值

除了基础类型中的数值类型可以直接从iOS中返回给U3D外,其他的类型是不能直接进行返回的,其中理由也很简单,因为非托管类型不能直接转换成托管类型。如果你想直接返回一个字符串给U3D,那么在运行时就会产生异常,因为转换成托管类型后他的内存由系统管理,一旦对象销毁他就会被释放内存,但它并不知道非托管模式下它是否被释放。

为了解决返回值的问题,其实可以借助上面提到的Marshal类型配合序列化的方式来进行返回值的返回:

  1. 先定义C代码中的接口
  1. void* returnString(int *len)
  2. {
  3. NSString *retStr = @"Hello World";
  4. *len = (int)retStr.length;
  5. char *nameBuffer = malloc(sizeof(char) * (retStr.length + 1));
  6. memcpy(nameBuffer, retStr.UTF8String, retStr.length);
  7. return nameBuffer;
  8. }
  1. 在C#中声明该接口
  1. [DllImport("__Internal")]
  2. static extern IntPtr returnString (out int len);
  1. 调用该接口,并解析返回参数值
  1. int strLen = 0;
  2. IntPtr stringData = returnString(out strLen);
  3. if (strLen > 0)
  4. {
  5. byte[] buffer = new byte[strLen];
  6. Marshal.Copy(stringData, buffer, 0, strLen);
  7. Marshal.FreeHGlobal(stringData);
  8. string str = Encoding.UTF8.GetString(buffer);
  9. Debug.Log(str);
  10. }

作者:杰嗒嗒的阿杰
链接:https://www.jianshu.com/p/1ab65bee6692
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Unity3D与iOS的交互的更多相关文章

  1. Unity3D与iOS的交互设计<ViewController 的跳转>

    原地址:http://www.aichengxu.com/article/%CF%B5%CD%B3%D3%C5%BB%AF/28797_12.html Unity3D与iOS的交互设计<View ...

  2. Unity3D与iOS消息交互方法(1)--iOS接收Unity3D发出的消息

    跨平台这种事情不管多NB, 总要有些与原生系统交互的方法, 比如  Unity3D与iOS消息交互方法. 一: 建立一个空的Unity工程. File -->  New Project 二: 编 ...

  3. UNITY3D与iOS交互解决方案

    原地址:http://bbs.18183.com/thread-456979-1-1.html 本帖最后由 啊,将进酒 于 2014-2-27 11:17 编辑 “授人以鱼,不如授人以渔”,以UNIT ...

  4. Unity3d与iOS交互开发——接入平台SDK必备技能

    原地址:http://www.2cto.com/kf/201401/273337.html# 前言废话:开发手机游戏都知道,你要接入各种平台的SDK.那就需要Unity3d与iOS中Objective ...

  5. Unity3d与iOS交互开发

    一.Unity3d  To  iOS: 最近要做一个商品和人体模型T台秀相关的功能,要用到Unity3D,搜集了一些资料先保存下来. 1.创建一个C#文件 SdkToIOS.cs 这是调用iOS函数的 ...

  6. Unity3d 与IOS 相互调用

    Unity3d 与IOS 相互调用 @灰太龙 群63438968 我用的Unity3d 4.2版本,这一节说一下IOS与U3D的交互! 首先在U3D中写个方法:这个时候导出为ios代码必须是真机,模拟 ...

  7. Unity3d调用iOS陀螺仪

    How to write gyroscope controller with Unity3d http://blog.heyworks.com/how-to-write-gyroscope-contr ...

  8. Unity3d开发IOS游戏 基础

    Unity3d开发IOS游戏 基础 @阿龙 -  649998群 1.先说明两个问题,我在WIN7下面的U3D里面,用了雅黑字体,但是导出为ios后,字体就看不见了,这是为什么呢?这是需要在MAC下找 ...

  9. H5与安卓、IOS的交互,判断微信、移动设备、安卓、ios

    一.通过用户代理可以判断网页当前所在的环境 var browser={ versions:function(){ var u = navigator.userAgent, app = navigato ...

随机推荐

  1. Qt 信号发射部分 undefined reference to错误

    在使用信号与槽很容易发生 undefined reference to 发射信号  ①继承QObject ②添加Q_OBJECT ③执行qmake ④构建 然后就可以运行啦!但是不知道是为什么,悄咪咪 ...

  2. flask-sqlalchemy同字段多条件过滤

    举例 from sqlalchemy import or_,and_# from operator import or_, and_ allapp = AppServer.query.filter(a ...

  3. JavaScript对象、函数、变量、字符串的处理、运算符

    一.对象 使用一种抽象的概念去描述,人{属性,方法} var car={type:"BYD",model:500,color:white,do:function(){"可 ...

  4. 笔记:Linux下软件的安装、CentOS更新yum源、LAMP环境搭建、kali软件管理

    一.Linux下软件的安装 方式:yum源 / rpm /源码安装 1.yum:(帮助管理员解决依赖关系) yum是通过分析rpm的包头数据后,根据各种软件的相关性质做出属性相对应的解决方案,然后可以 ...

  5. Nginx进程模型

    多进程模式 在开始介绍Nginx的进程模型之前先说明下:Nginx也支持Single Master单进程模式,但是这个模式效率较低,一般只用在开发环境.所以不是本文介绍的重点. Nginx默认采用多进 ...

  6. 【转】Windows10删除文件时却提示文件不存在的解决方案

    Windows10系统使用一段时间后用户都会定期进行删除清理系统垃圾,减少系统盘的容量占用,但在删除的过程中许多用户都有可能遇到无法删除的情况,如下为删除文件时却提示文件不存在的解决方案. 1.新建一 ...

  7. 哇,ElasticSearch多字段权重排序居然可以这么玩

    背景 读者提问:ES 的权重排序有没有示列,参考参考? 刚好之前也稍微接触过,于是写了这篇文章,可以简单参考下. 在很多复杂的业务场景下,排序的规则会比较复杂,单一的降序,升序无法满足日常需求.不过 ...

  8. CF208E Blood Cousins 题解

    一个奇奇怪怪的复杂度很垃圾的线段树合并解法 通过分析可以发现,要找$x$的$k$辈兄弟,只需要找到$x$的$k$辈祖先,然后查找以该祖先为根的子树中和$x$深度相同的节点个数$-1$即可.也就是说,询 ...

  9. failed to resolve org.junit.platform

    IDEA提示:failed to resolve org.junit.platform,如下图 方法一:修改Maven镜像 D:\Program Files\apache-maven-3.6.3-pc ...

  10. SwiftUI - Grid View 的实现方法,逐步剖析助你实现

    简介 在当前正式 SwiftUI 版本而言,很多控件都是缺少的.比如在 UIKit 框架里有 UICollectionView 组件,可以很方便地做 Gird 格子类型的视图.但是在 SwiftUI ...