Zephus 发表于 2022-6-15 08:21

Unity中使用ProtoBuf-保姆式教程

·ProtoBuf介绍

ProtoBuf 是结构数据序列化方法,可简单类比于 XML、JSON,其具有以下特点:
语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

·ProtoBuf获取

我这边选择的版本为 ProtoBuf 3.5.x
一共需要两个步骤
1.Google.Protobuf

从GitHub上获取完整版 传送门, 选择3.5.x的版本


这里有2条方式可供选择
(1)在unity中使用源码


Google.Protobuf这个文件夹直接放入到unity中
(2)在unity中使用Google.Protobuf.dll


使用vs打开它,用release去编译Google.Protobuf这个工程,在release这个目录下你会得到这个dll,放入到你的Libraries中
2.Protoc

从GitHub上获取windows版本 传送门, 选择3.5.1的版本


在bin中找到我们要的protoc.exe
写好 proto 文件之后用 protoc 编译器将 .proto文件编译成目标语言。



·.proto文件

测试文件内容
// 指定版本
syntax = "proto3";
// C#中的namespace
package ProtoTest

option optimize_for = SPEED;

// java文件路径
option java_package = "com.montior.proto";

// java文件名称
option java_outer_classname = "MonitorData";

// 消息结果。
message MsgResult {
    // 结果码。
    int32 code = 1;
    // 错误消息。
    string err_msg = 2;

}

// 接收包
message TaskProtocol {
    // 数据类型
    int32 packType = 1;

    // 具体数据
    bytes content = 3;

}

// 包的类型
enum PackType {
    LOGIN = 0;
    CREATE_TASK = 2;
    DELETE_TASK = 3;
}

message LoginPack{
    string username = 1;
}

message LoginPack2{
    string username = 1;
}

message CreateTaskPack{
    string taskId = 1;
    string taskName = 2;
}message:消息类型,类似于一个类
package:包名,CSharp中的命名空间,用来防止不同消息类型的冲突
enum:枚举,这个需要我说吗?
option:选项,说明下我这边用到的
option java_package = “com.example.foo”;// java文件路径
option java_outer_classname = “Ponycopter”;// java文件名称
option optimize_for = SPEED;//可以被设置为 SPEED, CODE_SIZE,or LITE_RUNTIME。这些值将通过如下的方式影响C++及java代码的生成:
注:以上选项,CSharp都用不着的,就是写着玩儿....
[*]数据类型   protobuf 数据类型
描述
C++
bool
布尔类型
bool
double
64位浮点数
double
float
32为浮点数
float
int32
32位整数、
int
uin32
无符号32位整数
unsigned int
int64
64位整数
__int64
uint64
64为无符号整
unsigned __int64
sint32
32位整数,处理负数效率更高
int32
sing64
64位整数 处理负数效率更高
__int64
fixed32
32位无符号整数
unsigned int32
fixed64
64位无符号整数
unsigned __int64
sfixed32
32位整数、能以更高的效率处理负数
unsigned int32
sfixed64
64为整数
unsigned __int64
string
只能处理 ASCII字符
std::string
bytes
用于处理多字节的语言字符、如中文
std::string

[*]关键字   指定字段说明required表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。optional表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。---因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。repeated表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。

·在Unity的布局

UnityProject
         -Asset
               --Libraries
                         ---Google.Protobuf      // Protobuf源文件/dll文件
                --Scripts/Editor/Proto2CSEditor      // 放置.proto文件转化为cs的代码
               --Scripts/ProtoMessage      // 放置转化的cs的代码
????      -Proto
               --monitorData.proto      //放置.proto文件
               --protoc.exe                  //
·如何使用protoc.exe把.proto文件转化为.cs

      
      public static void AllProto2CS()
      {
            string rootDir = Environment.CurrentDirectory;
            string protoDir = Path.Combine(rootDir, "Proto/");

            string protoc;
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                protoc = Path.Combine(protoDir, "protoc.exe");
            }
            else
            {
                protoc = Path.Combine(protoDir, "protoc");
            }

            string hotfixMessageCodePath = Path.Combine(rootDir, "Assets", "Scripts", "ProtoMessage/");

            string argument2 = $"--csharp_out=\"{hotfixMessageCodePath}\" --proto_path=\"{protoDir}\" monitorData.proto";

            Run(protoc, argument2, waitExit: true);

            UnityEngine.Debug.Log("proto2cs succeed!");

            AssetDatabase.Refresh();
      }

      public static Process Run(string exe, string arguments, string workingDirectory = ".", bool waitExit = false)
      {
            try
            {
                bool redirectStandardOutput = true;
                bool redirectStandardError = true;
                bool useShellExecute = false;
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                  redirectStandardOutput = false;
                  redirectStandardError = false;
                  useShellExecute = true;
                }

                if (waitExit)
                {
                  redirectStandardOutput = true;
                  redirectStandardError = true;
                  useShellExecute = false;
                }
               
                ProcessStartInfo info = new ProcessStartInfo
                {
                  FileName = exe,
                  Arguments = arguments,
                  CreateNoWindow = true,
                  UseShellExecute = useShellExecute,
                  WorkingDirectory = workingDirectory,
                  RedirectStandardOutput = redirectStandardOutput,
                  RedirectStandardError = redirectStandardError,
                };
               
                Process process = Process.Start(info);

                if (waitExit)
                {
                  process.WaitForExit();
                  if (process.ExitCode != 0)
                  {
                        throw new Exception($"{process.StandardOutput.ReadToEnd()} {process.StandardError.ReadToEnd()}");
                  }
                }

                return process;
            }
            catch (Exception e)
            {
                throw new Exception($"dir: {Path.GetFullPath(workingDirectory)}, command: {exe} {arguments}", e);
            }
      }
·序列化与反序列化



·举个栗子

      MsgResult result = new MsgResult
      {
            Code = -999,
            ErrMsg = "Error"
      };

      TaskProtocol msgResult = new TaskProtocol
      {
            PackType = 111,
            Content = result.ToByteString()
      };

      byte[] s = packer.SerializeTo(msgResult);
      Debug.Log("---------------------------------------------------");


      TaskProtocol response = new TaskProtocol();
      packer.DeserializeFrom(response, s);

      MsgResult responseMsgResult = new MsgResult();
      packer.DeserializeFrom(responseMsgResult, response.Content.ToByteArray());

      Debug.Log(response.PackType);
      Debug.Log(responseMsgResult.Code);
      Debug.Log(responseMsgResult.ErrMsg);
·Unity中还可以使用protobuf-net.dll

<!--暂无-->
·结语

如果这样你都还配置不好ProtoBuf,不知道怎么序列化和反序列化的话!!!!
那就多看几遍,哈哈哈

页: [1]
查看完整版本: Unity中使用ProtoBuf-保姆式教程