Unity C# 编译集成 Google Protobuf
踩了很多坑,因为网上几年前基本上都是用第三方 protobuf-net
库来作为 C# 的 Protobuf 工具使用的,尤其是 Unity 。所以关于 Google 官方的 C# 集成到 Unity 基本没人使用,C# 非 Unity 的开发(比如 .net )可以通过VS的 Nuget 快速集成 Protobuf,在 Unity 上这样做只能够在 VS 编辑器下检测到这个库,而 Unity 编辑器是不知道的,所以不能用这种方式。
而直接接入源码也会因为缺少依赖报错无法解决,最终还是采用自己编译出DLL集成的方式来接入。
另外前提是 Unity 切换成 .net4.x
,因为 Google Protobuf 需要 4.5+。
编译DLL
源码:https://github.com/protocolbuffers/protobuf/releases
使用VS打开目录 protobuf-3.14.0\csharp\src
中的 Google.Protobuf.sln
如图选择:
然后生成
生成结束后 protobuf-3.14.0\csharp\src\Google.Protobuf\bin\Release\net45
目录会生成以下文件:
把这些文件复制到 Unity 项目的 Assets/Plugins
目录,这样就算集成好了序列化和反序列化的库。
导出Proto
集成之后只是有了序列化和反序列化的基础库,但是没有数据结构,所以还需要通过 .proto
文件编译生成C#可以使用的数据结构。
用 protoc.exe:
1 | protoc --csharp_out=. *.proto |
把多个proto分别生成 cs
文件,放到项目里合适的目录即可。
序列化与反序列化
在需要使用的地方引入 Protobuf:
1 | using Google.Protobuf; |
构造Message数据:
1 | Person person = new Person |
序列化:
1 | byte[] result = person.ToByteArray(); |
反序列化:
1 | Person p = Person.Parser.ParseFrom(result); |
修改网络层接口
最后是要把所有以前调用 protobuf-net
的地方改掉,另外 Google Protobuf 的接口类是 IMessage。
泛型方法反序列化
在知道类型的情况下是很容易反序列化 Proto 数据的,尤其是可以把对应的 Type 作为函数泛型约束的情况。
即使是只知道 Type 的时候,在 protobuf-net
中也提供了反射反序列化的接口。不过 Google 官方没有提供反射解析的方式,因此只能使用泛型方法反序列化。
这里我提供两种泛型方法用于反序列化,这两种都是可以用的:
1 | //一种泛型反序列化方法 |
工厂模式函数反序列化
在不知道类型的情况下怎么反序列化 Proto 数据?服务器发给我们的数据只会有消息ID和二进制数据,这种情况怎么反序列化?
一般来说,需要在游戏启动时提前实例化好需要用到的消息的解析器,并保存与消息ID的对应关系,用于之后的序列化和反序列化。
不推荐使用反射的方式创建,而且 Google 官方也没提供反射解析的方式,因此想保存消息Type与消息ID的映射是没用的。所以我们写一个静态类,里面提供一个工厂方法,用于提供指定类型消息的解析器对象:
1 | //Proto 消息解析器创建类 |
同时,在游戏启动时也要创建对应的字典,来保存消息ID和解析器的对应关系,这里就不写了。
注意事项
最后要注意的是 Google 官方的 C# Protobuf 相比 protobuf-net
还有一些区别:
- 所有消息的字段都会强制转为驼峰命名并去掉下划线,并且没有办法避免;
- 消息字段不能再使用byte数组,必须使用 Google 封装的 ByteString,并且它是只读的;
- 不提供通过反射进行反序列化的接口,也就是不能通过把消息的 Type 作为参数传递来反序列化。
前两点还是挺坑的,命名导致从 protobuf-net
换为官方 Protobuf 成本很高,要修改的太多,然后不能使用 byte 数组导致需要加密的时候必须对 ByteString 进行复制转为 byte 数组然后解密。
不过还是更换为官方的 Protobuf 了,因为最新的解决了 GC 的问题,然后避免了反射的调用(其实是强制的)。