踩了很多坑,因为网上几年前基本上都是用第三方 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
下载 protobuf-csharp-3.14.0.zip
使用VS打开目录 protobuf-3.14.0\csharp\src 中的 Google.Protobuf.sln
如图选择:
image-20201117164601808
然后生成
image-20201117164624330
生成结束后 protobuf-3.14.0\csharp\src\Google.Protobuf\bin\Release\net45 目录会生成以下文件:
image-20201117164722068
把这些文件复制到 Unity 项目的 Assets/Plugins 目录,这样就算集成好了序列化和反序列化的库。
导出Proto
集成之后只是有了序列化和反序列化的基础库,但是没有数据结构,所以还需要通过 .proto 文件编译生成C#可以使用的数据结构。
用 protoc.exe:
protoc –csharp_out=. *.proto
把多个proto分别生成 cs 文件,放到项目里合适的目录即可。
序列化与反序列化
在需要使用的地方引入 Protobuf:
using Google.Protobuf;
构造Message数据:
Person person = new Person
{
Age = 22,
Address = “111111”,
Name = “John”
};
序列化:
byte[] result = person.ToByteArray();
反序列化:
Person p = Person.Parser.ParseFrom(result);
修改网络层接口
最后是要把所有以前调用 protobuf-net 的地方改掉,另外 Google Protobuf 的接口类是 IMessage。
泛型方法反序列化
在知道类型的情况下是很容易反序列化 Proto 数据的,尤其是可以把对应的 Type 作为函数泛型约束的情况。
即使是只知道 Type 的时候,在 protobuf-net 中也提供了反射反序列化的接口。不过 Google 官方没有提供反射解析的方式,因此只能使用泛型方法反序列化。
这里我提供两种泛型方法用于反序列化,这两种都是可以用的:
//一种泛型反序列化方法
public T Deserialize<T>(Stream s) where T : IMessage<T>, new()
{
T message = new T();
message.MergeFrom(s);
return message;
}
//另一种泛型反序列化方法
public T Deserialize2<T>(Stream s) where T : IMessage<T>, new()
{
MessageParser<T> parser = new MessageParser<T>(() => new T());
return parser.ParseFrom(s);
}
工厂模式函数反序列化
在不知道类型的情况下怎么反序列化 Proto 数据?服务器发给我们的数据只会有消息ID和二进制数据,这种情况怎么反序列化?
一般来说,需要在游戏启动时提前实例化好需要用到的消息的解析器,并保存与消息ID的对应关系,用于之后的序列化和反序列化。
不推荐使用反射的方式创建,而且 Google 官方也没提供反射解析的方式,因此想保存消息Type与消息ID的映射是没用的。所以我们写一个静态类,里面提供一个工厂方法,用于提供指定类型消息的解析器对象:
//Proto 消息解析器创建类
static class Parser
{
public static MessageParser Get<T>() where T : IMessage<T>, new()
{
MessageParser<T> parser = new MessageParser<T>(() => new T());
return parser;
}
}
同时,在游戏启动时也要创建对应的字典,来保存消息ID和解析器的对应关系,这里就不写了。
注意事项
最后要注意的是 Google 官方的 C# Protobuf 相比 protobuf-net 还有一些区别:
所有消息的字段都会强制转为驼峰命名并去掉下划线,并且没有办法避免;
消息字段不能再使用byte数组,必须使用 Google 封装的 ByteString,并且它是只读的;
不提供通过反射进行反序列化的接口,也就是不能通过把消息的 Type 作为参数传递来反序列化。
前两点还是挺坑的,命名导致从 protobuf-net 换为官方 Protobuf 成本很高,要修改的太多,然后不能使用 byte 数组导致需要加密的时候必须对 ByteString 进行复制转为 byte 数组然后解密。
不过还是更换为官方的 Protobuf 了,因为最新的解决了 GC 的问题,然后避免了反射的调用(其实是强制的)。