更新

  • 2021/03/06 增加 JSON-RPC 2.0 规范推荐
  • 2020/01/05 初次发布

介绍

Unity 游戏经常需要接入各种 SDK 实现支付、登录、分享等功能,但是很多项目使用了在 C# 中增加 SDK 接口的方法导致接入不同渠道 SDK 时需要反复打包,非常耗费时间。

前提

使用 Lua 编写逻辑的 Unity 项目。

需求

接入 SDK 时会使用 C# 编写游戏逻辑与 SDK 之间的接口,因此每增加一个新的 SDK 都需要新增对应的 C# 接口,最终导致打不同版本的 SDK 包必须使用 Unity 输出不同的工程。

这极大地浪费时间,打包耗时也随着 SDK 数量的增多而直线上升。如何降低时间呢?从现状分析可以发现耗时最大的部分是 Unity 生成工程,因此降低时间最直接的就是减少反复生成不同版本 Unity 工程耗费的时间。

速度最快的代码是不运行的代码,因为耗时为 0。由此可以推断出最好是只生成一次工程,去掉所有不必要的重复导出工程过程,不同的 SDK 共用一份生成的工程打包。

机制

需求是使用 Unity 生成一份工程后接入许多不同的 SDK,从而节省 Unity 打包时间。

由于 Unity 生成的工程不常变化,而游戏逻辑与 SDK 经常变化(接入不同 SDK、SDK 版本更新)。可以将代码简单地分为 C# 代码(Unity 工程内的代码)与非 C# 代码(Unity 生成的工程代码,包括 Java、Objective-C、Lua 等等),然后分别放到不同的工程内进行打包。

但是游戏逻辑需要与 SDK 之间进行通信,如何实现呢?通信的本质实际是 Remote procedure call - Wikipedia,这个就是平时客户端与服务器通信时使用的机制。而客户端与服务器之间是通过 socket 连接,类似地,游戏逻辑与 SDK 之间也需要这样的通道。

实现

通道

游戏逻辑只能通过 Unity 的 C# 与 SDK 进行通信。不同的 SDK 有不同的接口,且 SDK 升级后接口也会发生改变,因此需要将变化的部分从 C# 中移动到 SDK 中,C# 只提供基本的通道功能。

异步

C# 定义接口可以参考 RPC,即要实现调用与返回两个接口,因为调用本身可能是异步的,例如一部分 SDK API 是网络请求。

协议

协议内容可以尝试使用 JSON,因为 JSON 在各大平台上几乎都是内置支持的。当然这里正确的做法是使用 IDL 生成,例如 Protocol Buffers  |  Google Developers,考虑到 SDK 接口与内容并不复杂,可以手动维护两边的数据结构。

  1. 需要提供向前兼容性,即可以随意新增参数,可以适配 SDK 的新版本。
  2. 需要提供向后兼容性,这里的后是指的其他 SDK,因为一个 SDK 接口变化后需要不影响其他 SDK 接口。

这里推荐使用现成的 JSON-RPC 规范:

规范已经规定了版本号、方法名、参数、ID 之类的名字,使用规范有助于减少命名冲突。参数建议使用键值对的字典形式,有助于后续增加参数或调整参数顺序。

注意:为了兼容性,尽量不要删除参数,只管新增参数就可以维持最大的兼容性。

代码

以下代码只是给出一个范例,考虑到接收回调必须使用 MonoBehaviour 定义接收回调方法,由于这只是一个简单的演示代码,因此将其做成一个单例。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class NativeChannel : MonoBehaviour
{
    private NativeChannel _instance;

    public NativeChannel Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new GameObject("NativeChannel");
                DontDestroyOnLoad(_instance)
            }

            return _instance;
        }
    }

    public Action<string, string> Callback;

    public string CallNative(string method, string data)
    {
        #if UNITY_IOS
            _iOSCallNative(method, data);
        #elseif UNITY_ANDROID
            AndroidJavaObject.Call(method, data)
        #else
            _callNative(method, data);
        #endif
    }

    public void NativeCallback(string method, string data)
    {
        if (Callback != null)
        {
            Callback.Invoke(method, data);
        }
    }
}

在定义完 C# 通道后,游戏逻辑使用 Lua 代码编写,SDK 根据平台使用对应的 Java 或 Objective-C 之类的代码编写,可以根据 method 名字派发到不同的方法中。

建议

Mockup 摸拟对象

单元测试中存在 Mockup 摸拟对象这个概念,用于替代真实对象的行为,在这里可以借用这个概念来实现以下 SDK:

  1. 可以考虑为 Unity 编辑器做一个单独的编辑器版本 SDK,可以用于在编辑器中模拟 SDK 的行为,这样调试起来比较方便。
  2. 另外也建议准备一份空版本 SDK,类似编辑器版本 SDK,只不过是用于真机上不接任何 SDK 时使用,这是模仿 Unity 命令行打包时使用的 NULL Graphics Device。

工程建议

  • SDK 需要将 Demo 与文档作为产物放在 Release 仓库中,与原始仓库分离。
  • 文档建议生成 PDF,可以包含图片,考虑使用 Markdown、Asciidoc 之类的标记语言,这样文档的格式是统一的,而不会出现到处是花花绿绿的颜色与格式。
  • C# 中尽可能不使用宏定义,而是使用平台判断 API。可以防止宏定义导致的代码引用查找不全,静态分析遗漏宏定义中的代码。