Unity 和 iOS 交互(通用数据)

背景

依稀记得3年前第一次接触到Unity,惊叹他的强大能力,也初次接触到音视频领域,还接触了一些摄影相关能力(感谢@老晋哥)。
转眼至今公司很重视音视频新产品,逐渐的公司也把一些核心能力放到了Unity这个平台上。
今天要分享的交互方式也很好支持着沙拉视频Blurrr等新产品,相信不久还会支持更多的新产品。
那么Unity和iOS数据交互效率就至关重要了,如何打造一种高效的交互方式,成了这2年来很重要的事情。

过程&实现

iOS 向 Unity 发送消息

这种情况下一般使用UnitySendMessage的方法来实现,看看官方文档怎么说

使用 UnitySendMessage 时具有以下限制:

1.通过原生代码,只能调用与以下签名对应的脚本方法:void MethodName(string message);。 2.对 UnitySendMessage 的调用是异步的,并有一帧延迟。 3.如果两个或多个游戏对象具有相同的名称,则在使用 UnitySendMessage 时可能导致冲突。

无论使用什么方式调用UnitySendMessage方法,方法最终会使用主线程运行。

假如客户端有一个渲染需求,然后呢,Unity这边正好有一个接口满足,叫 LoadRender,代码如下

iOS 这边需要简单封装一下方便调用

/*
iOS
Objc++
*/
- (void)api_LoadRender:(NSString *)json
{
  // RenderController 表示类名
  //LoadRender 表示方法名
  // [json UTF8String] 参数
  UnitySendMessage("RenderController", "LoadRender", [json UTF8String]);
}

Unity这边也需要有对应的方法实现

/*
Unity
C#
*/ 
public class RenderController
{
  public void LoadRender(string json)
  {
    // 处理这次调用
    // Unity 向 iOS 发送消息 告诉是成功或者失败
    ...
  }
}

Unity 向 iOS 发送消息

先看一个最简单的示例
Unity当中声明一个方法

/*
Unity
C#
*/
[DllImport("__Internal")]
internal extern static void calliOS_method1();
/*
iOS
Objc++
UNITY_INTERFACE_EXPORT 和 UNITY_INTERFACE_API 在 Unity的IUnityInterface.h 中声明
*/
extern "C"
{
  //实现方法
  void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API calliOS_method1()
  {
    // 运行逻辑1
  }
}

这样调用calliOS_method1时,客户端就及时收到并运行了代码,注意哦!这里是及时响应的。
当然业务多了之后,cs当中定义方法也会增多,维护成本也开始变高。
到目前为止,客户端想要回传数据给Unity端还不能支持。
所以我们得改造一下,支持返回值的方法来试试。

1. 重新定义下接口,使之能通用起来

cs 代码中声明一个通用方法,有一个指针输入,和一个指针返回

/*
iOS
Objc++
*/
[DllImport("__Internal")]
internal extern static IntPtr onETPlatform(IntPtr entity);

这样的话就可以根据业务需求再定义输入结构
这时,上面的LoadRender方法就可以加入回调能力,
当客户端通过api_LoadRender发起API调用后,Unity下一帧运行好后就调用通用接口回调给iOS端

2. 双边定义相同结构

/*
Unity
C#
*/
public struct ETPlatformAPICallBack
{
  public int type;         //结
  public int componentType;//构
  public int status;       //一
  public double re_status; //致
}

/*
iOS
Objc++
*/
extern "C"
{
  typedef struct _ETPlatformAPICallBack
  {
    int type;         //结
    int componentType;//构
    int status;       //一
    double re_status; //致
  } ETPlatformAPICallBack;
}

Unity端的LoadRender方法做完后,需要创建一个 ETPlatformAPICallBack 对象,并且赋值(表示状态),再通过一个简单方法转换成 一个IntPtr 对象,如下

/*
Unity
C#
*/
// T是范型
private static IntPtr StructToIntPtr<T>(T a)
{
  IntPtr sp = Marshal.AllocCoTaskMem(Marshal.SizeOf(a));
  Marshal.StructureToPtr(a, sp, false);
  return sp;
}

转换了对象后,调用onETPlatform 通用接口,传入刚才转义好的结构体,iOS端就可以及时收到消息

3.在iOS端实现onETPlatform方法

C# 的 IntPtr 对应 void*

/*
iOS
Objc++
*/
extern "C"
{
  void* UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API onETPlatform(void* entity)
  {
    //由于所有的struct 都必须是 int type 开头,所以我拿到指针的前面位置转成int,然后判定是什么类型
    int32_t *p_int = static_cast<int32_t *>(entity);
    int type = p_int[0];
    switch(type) {
      case ETUnityPlatformStructType_LoadRenderCallBack: // 调用LoadRender 接口后回调
      {
         ETPlatformAPICallBack *cety = static_cast<ETPlatformAPICallBack *>(entity);
         int status = cety.status; //使用 Unity 传过来的值 定义0代表成功 -1表示失败
         cety.re_status = 1.2; //把新值传入到 Unity 端
      } break;
      default:
        assert(false); // 不支持
        break;
    }
    return entity;
  }
}

稍微解释一下
onETPlatform方法接收到一个entity,然后把entity强转成int指针,获取指针位置0的int值
通过type值确定它的所属struct类型,所以每一个struct的第一个字段都是int类型
然后再static_cast转换成对应的struct,然后根据业务使用字段或者修改字段
最后直接返回entity即可(因为是指针操作)。

4.Unity获取返回值

完成调用后会收到IntPtr对象,再把这个对象转换成strcut,如下

/*
Unity
C#
*/
private static T IntPtrToStruct<T>(IntPtr ptr)
{
  return (T)Marshal.PtrToStructure(ptr, typeof(T));
}

最后拿到iOS的返回数据

关键代码

// Unity C#
public class RenderController
{
  private T InvokePlatform_iOS<T>(T entity)
  {
    IntPtr sp = StructToIntPtr(entity); // 1 把要给iOS的 C# struct 转成 指针
    var ptr = onETPlatform(sp);// 把数据传入到iOS端
    var ver = IntPtrToStruct<T>(ptr);// 把iOS端的返回数据转换成 C# 的struct 对象
    Marshal.FreeCoTaskMem(sp);// 释放
    return ver;
  }
  // 第一节讲过
  public void LoadRender(string json)
  {
    // 处理这次调用
    // Unity 向 iOS 发送消息 告诉是成功或者失败 的伪代码
    ...
    var cb = new ETPlatformAPICallBack(); // 创建对象
    cb.type = ETUnityPlatformStructType_LoadRenderCallBack;// 这里要保证和iOS的枚举一致
    cb.status = 0;// 0=成功  -1=失败(-1失败码)
    var ety = InvokePlatform_iOS(cb); // 请求客户端,并获取客户端返回的数据
    // ety.re_status 值为客户端写入值 1.2
  }
}

至此,交互方式分享就到这里结束了。

相关注意:

1.客户端也分为iOS和MacOS,之所以有2个平台是因为MacOS上方便测试,还有一些效果打包需要在MacOS上去做,所以这2个平台需要解耦,做法是专门写了基类,iOS和MacOS分别有继承它。

2.如果struct没有统一,会出现比较严重的问题,这个问题是内存被改写引发的不可知问题类型,可能就崩溃,可能是值改变引起的连锁反应吧,所以这个strcut 一定一定一定要保持参数结构统一。

3.因为这些接口调用比较频繁(一帧里好几次,甚至几十次,取决于需要交互的数据量)所以建议entity new出来后就不要销毁了(多线程就new吧)。

参考资料

构建适用于 iOS 的插件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容