返回 登录
0

Unity实战之Protobuf案例应用

Protobuf 全称Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,很适合做数据存储或 RPC 数据交换格式。它可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python、C#等多种语言的 API。Protobuf是google开源的序列化和反序列化工具,主要是用在网络游戏的消息结构体定义上。它相对于XML文件和Json文件性能更好,效率更高,在在网站 http://code.google.com/p/protobuf/downloads/list上可以下载 Protobuf 的源代码,它的文件格式是以.proto为扩展名的文件。为了便于大家更好的理解其使用原理,下面就通过案例的方式给大家讲解。

Protobuf消息结构体定义
在使用Protobuf定义消息结构体时首先要搞明白其语法结构,Protobuf定义的消息由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式。其限定修饰required、optional、repeated三个修饰符。
Required修饰符表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。
Optional修饰符表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。—因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。
Repeated:表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值,可以看作是在传递一个数组的值或者List列表数值。开发网络游戏时,经常会定义消息结构体,这些消息结构体会在客户端和服务器中都会用到,所以只需要定义一套就可以了,现在移动端大部分用户都在使用Unity引擎开发,所以这些结构体需要转成C#语言。下面开始从结构体定义开始讲起。
编写Protobuf结构体
网络消息的定义通常会使用json文件或者二进制文件或者自定义结构体,现在使用protobuf定义消息结构体的公司越来越多,逐渐成为消息结构体定义的主流,这也要感谢google提供了一个开源的序列化和反序列化工具。本节以实际项目开发的案例给大家介绍一下网络消息结构体的定义,任何大型网络游戏都有角色的定义。首先从角色的定义说起,角色消息包括很多的属性。定义结构体如下所示,其中message是结构体的修饰符,是必须的。结构体定义如下所示:
//角色信息结构

message msgcharinfo
{
    optional uint32 uaid = 1;    //用户ID
    optional uint32 charid = 2;  //角色ID
    optional uint32 kind = 3; //角色种类
    optional string name = 4;  //角色名字
    optional string head = 5;   //头像ID
    optional uint32 level = 6;  //角色等级
    optional uint32 exp = 7;  //角色经验
    optional uint32 phypower = 8;   //物理攻击
    optional uint32 leadership = 9;  //领导标记
    optional uint32 friendnum = 10;   //朋友数量
    optional uint32 gamecoin = 11;   //游戏货币
    optional uint32 diamond = 12;  //钻石数量
};

以上是网络游戏中完整的角色定义,包括用户id,游戏中角色id等信息。它存放的文件扩展名为.proto。结构体中的各个项的修饰都是optional,也就是可选项,可以不用赋值。protobuf自身定义的文件是文本文件。如果将该文件直接放到Unity工程中,Unity是不会识别的。这就需要将其转成Unity可识别的脚步C#文件。再举一个例子,还是结构体定义是枚举定义代码如下所示:
//初始化角色奖励信息

enum enumGetCharRewardResult
{
    Success  = 0;           //成功获取角色
    SystemError = 1;        //系统错误
    NewChar = 2;            //创建新角色奖励信息
};
该结构体是以enum修饰的枚举类型,里面有三项,枚举定义跟C++或者Java定义的类似,枚举定义的内部成员不需要任何符号修饰。假设以上内容是在文件common.proto文件定义的,下面我们再定义一个proto文件,其内容如下所示:
package clientmsg;
import "common.proto";
message C2SNameRepetition
{
    optional msgcharinfo charinfo = 1;    //创建角色
    optional uint32 mapid = 2;
    optional uint32 cityid = 3;
};
给大家解释一下代码,第一行package clientmsg;表示的是模块的封装,其含义类似C++或者C#的namespace命名空间,第二行import "common.proto";表示的是引用该文件,目的是需要用到该文件已定义的结构体,例如message C2SNameRepetition定义的内容中的语句optional msgcharinfo charinfo = 1;它引用的是common.proto文件中已定义的msgcharinfo的结构体,protobuf支持这种引用关系,从中可以看出Protobuf语言也是比较灵活的,文件与文件之间是可以互相引用的,接下来开始介绍转换工具的制作了。

Protobuf转换工具制作
定义好了proto文件后,如果直接放到unity中,它只能被作为文本文件,这不是开发者想要的,因为要在程序中使用定义好的结构体,需要一个能将其转换成C#脚本文件的工具。下面开始告诉大家制作该工具需要做哪些工作?制作工具需要的库文件可以在网上下载到,就是已编译好的库工程,主要内容如下图所示:
图片描述

接下来需要写一个批处理文件执行proto批量转换操作,假设上述库文件是在文件路径:3Party\protobuf-net\net下面。制作的工具文件的扩展名为.bat。批处理文件完整内容如下所示:
@echo off
set tool=..\3Party\protobuf-net\net

rem ===============================================
rem  Support
set proto=common.proto
%tool%\protogen.exe -i:%proto% -o:%proto%.cs -q

set proto=login.proto
%tool%\protogen.exe -i:%proto% -o:%proto%.cs -q

set proto=begingame.proto
%tool%\protogen.exe -i:%proto% -o:%proto%.cs -q
pause
其中语句set tool=..\3Party\protobuf-net\net表示的是库文件所在的目录,set proto=common.proto表示的是要转换的proto文件名字,%tool%\protogen.exe -i:%proto% -o:%proto%.cs -q表示的是调用上述目录下的库,将common.proto转化成common.proto.cs文件,以此类推,因为我们定义的commo.cs是公用的文件,下面的文件都会引用到该文件的内容,同时把proto文件拷贝到与扩展名bat相同的文件夹下面。其执行的效果如下图所示:

图片描述

make-protobuf.bat就是执行的批处理程序,执行的结果就是生成对应的cs文件,然后将cs文件拖放到Unity的目录下面,生成的cs文件内容如下所示,以login.proto.cs文件为例,文件内容如下图4-3所示:
图片描述

限于篇幅所限,只截取一部分内容,第一行表示的是引用common,再下面是命名空间以及类声明,眼尖的读者可能注意到了一个细节就是类前面的修饰符partial,给大家介绍一下,它属于一个局部类型,局部类型允许我们将一个类、结构或接口分成几个部分,分别实现在几个不同的.cs文件中。给大家普及一下partial的基础知识,一是类型特别大,不宜放在一个文件中实现;二是一个类型中的一部分代码为自动化工具生成的代码,不宜与我们自己编写的代码混合在一起;三是需要多人合作编写一个类。这几种情况适用于partial修饰。接下来介绍一下如何在Unity中使用。
Protobuf文件在Unity中运用
在Unity中使用定义的Protobuf文件,首先需要把Protobuf-net的源文件放到Unity目录下,源文件的下载地址是:
https://github.com/mgravell/protobuf-net/tree/master/protobuf-net,使用源文件的目的是可以实现Protobuf在android和ios平台同时使用,代码可以直接在Google提供的官网上直接下载,拖放到Unity中的效果如下图4-4所示
图片描述

这些前期工作完成后就可以直接调用Protobuf源代码中的库函数进行序列化和反序列化。下面将生成的cs脚本文件拖放到Unity中,效果如下图所示:

图片描述

接下来介绍一下如何在Unity中使用,在使用定义好的结构体时,需要在文件中加入引用头文件的using clientmsg,然后在函数中首先new一个对象如下所示:   
clientmsg.c2s_login msg = new clientmsg.c2s_login();

然后再对其结构体填充数值,如下语句所示:

    msg.name = Global.userInputName;
        msg.pwd = Global.password;
        messageContentLen += msg.name.Length;
        messageContentLen += msg.pwd.Length;
最后可以将其结构体发送到服务器:SendProtoBufMsg(msg, awnet);这样整个Protobuf文件的使用就结束了,希望对大家有所帮助,具体详情查看书籍《Unity3D实战核心技术详解》一书。

笔者简介姜雪伟个人网页

评论