ProtoBuf的使用有两种方式,一种是以.proto
文件中转的方式,另一种是使用“protobuf-net”的方式,这里介绍第二种
获取Protobuf-net
GitHub - protobuf-net/protobuf-net: Protocol Buffers library for idiomatic .NET
首先在Visual Studio里面点击“工具——NuGet包管理器——管理解决方案的NuGet程序包”。
在打开的窗口中选择“浏览”标签页。输入“protobuf-net”点击搜索。点击protobuf-net,在右侧点击项目,选择最新稳定版并安装。
此时又会弹出一个窗口,要求添加新的引用,点击确定。
注意:
在Unity主工程中,可能会打不开“管理解决方案的NuGet程序包”窗口,我们可以在HotFix工程中打开此窗口并安装protobuf-net,但是要注意,安装后,我们在右侧的“解决方案资源管理器——引用”中,删除protobuf-net.dll
、protobuf-net.Core.dll
、System.Buffers.dll
、System.Collections.Immutable.dll
、System.Memory.dll
、System.Numerics.Vectors.dll
、System.Runtime.CompilerServices.Unsafe.dll
进入“C:\Users\zerwa\.nuget\packages
”文件夹,我们需要将一些dll库复制到Unity的Assets——Plugins——protobuf文件夹里面
C:\Users\zerwa\.nuget\packages\protobuf-net\3.2.26\lib\net462\protobuf-net.dll
C:\Users\zerwa\.nuget\packages\protobuf-net.core\3.2.26\lib\net462\protobuf-net.Core.dll
C:\Users\zerwa\.nuget\packages\system.collections.immutable\7.0.0\lib\net462\System.Collections.Immutable.dll
C:\Users\zerwa\.nuget\packages\system.memory\4.5.5\lib\net461\System.Memory.dll
C:\Users\zerwa\.nuget\packages\system.numerics.vectors\4.5.0\lib\net46\System.Numerics.Vectors.dll
C:\Users\zerwa\.nuget\packages\system.runtime.compilerservices.unsafe\6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll
上面只是示例的dll库位置,不同的版本用作参考即可
Unity准备
在GameData——Data文件夹内新建“ProtobufData”文件夹
选中Assets——ResFrame——Editor——Resource中的“ABConfig”,添加一个All File Dir AB
1 2
| AB Name:protobuffdata Path:Assets/GameData/Data/ProtobufData
|
编辑器转换工具
ProtoBuff作为二进制文件的替代,我们也需要完善好“XmlToProto”的方法
修改ResFrameConfig
脚本,使用这个脚本配置生成protobuf二进制文件时保存的文件夹路径。
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
| using UnityEngine; using UnityEditor; [CreateAssetMenu(fileName ="ResFrameConfig",menuName ="CreateResFrameConfig",order = 2)] public class ResFrameConfig : ScriptableObject { [Tooltip("存放protobuf二进制文件的文件夹路径")] public string m_ProtobufPath = "Assets/GameData/Data/ProtobufData/"; } [CustomEditor(typeof(ResFrameConfig))] public class ResFrameConfigInspector : Editor { SerializedProperty m_ProtobufPath;
private void OnEnable() { m_ProtobufPath = serializedObject.FindProperty("m_ProtobufPath"); } public override void OnInspectorGUI() { EditorGUILayout.PropertyField(m_ProtobufPath, new GUIContent("Protobuf二进制文件路径")); EditorGUILayout.Space(5); serializedObject.ApplyModifiedProperties(); } }
|
如果想要修改具体的路径,在Assets——ResFrame——Editor文件夹内点击“ResFrameConfig”并修改即可
修改BinarySerializeOpt
,添加Protobuf序列化和反序列化的方法
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| public class BinarySerializeOpt { public static bool ProtoSerialize(string path, System.Object obj) { try { using (Stream file = File.Create(path)) { ProtoBuf.Serializer.Serialize(file, obj); return true; } } catch (Exception e) { Debug.LogError("类转protobuf失败:" + obj.GetType() + "," + e.Message); } return false; } public static T ProtoDeserialize<T>(string path) where T : class { T t = default(T); try { using (Stream file = File.OpenRead(path)) { t = ProtoBuf.Serializer.Deserialize<T>(file); } } catch (Exception e) { Debug.LogError("proto反序列化失败:"+ path + "," + e.Message); throw; } return t; } public static byte[] ProtoSerialize(System.Object obj) { try { using(MemoryStream ms = new MemoryStream()) { ProtoBuf.Serializer.Serialize(ms, obj); byte[] result = new byte[ms.Length]; ms.Position = 0; ms.Read(result, 0, result.Length); return result; } } catch (Exception e) { Debug.LogError("类转protobuf(bytes[])失败:" + obj.GetType() + "," + e.Message); } return null; } public static T ProtoDeserialize<T>(byte[] msg) where T : class { T t = default(T); try { using (MemoryStream ms = new MemoryStream()) { ms.Write(msg, 0, msg.Length); ms.Position = 0; t = ProtoBuf.Serializer.Deserialize<T>(ms); } } catch (Exception e) { Debug.LogError("proto(byte[])反序列化失败:" + e.Message); throw; } return t; } }
|
修改DataEditor
脚本
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| public class DataEditor { public static string ProtobufPath = ResConfig.GetResFrameConfig().m_ProtobufPath; [MenuItem("Assets/Xml转Protobuf")] public static void AssetsXmlToProtobuf() { UnityEngine.Object[] objs = Selection.objects; for (int i = 0; i < objs.Length; i++) { EditorUtility.DisplayProgressBar("已经选中的xml转成protobuf", "正在扫描" + objs[i].name + "……", 1.0f / objs.Length * i); XmlToProtobuf(objs[i].name); } AssetDatabase.Refresh(); EditorUtility.ClearProgressBar(); } [MenuItem("Tools/Xml/所有Xml转Protobuf")] public static void AllXmlToProtobuf() { string xmlFullPath = Path.GetFullPath(XmlPath); string[] filesPath = Directory.GetFiles(xmlFullPath, "*.*", SearchOption.AllDirectories); for (int i = 0; i < filesPath.Length; i++) { EditorUtility.DisplayProgressBar("文件夹下所有的xml转二进制", filesPath[i], 1.0f / filesPath.Length * i); if (filesPath[i].EndsWith(".xml")) { string binaryName = Path.GetFileNameWithoutExtension(filesPath[i]); XmlToProtobuf(binaryName); } } AssetDatabase.Refresh(); EditorUtility.ClearProgressBar(); } private static void XmlToProtobuf(string name) { if (string.IsNullOrEmpty(name)) return; try { Type type = null; foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) { Type curType = assembly.GetType(name); if (curType != null) { type = curType; break; } } if (type != null) { string xmlPath = XmlPath + name + ".xml"; string protobufPath = ProtobufPath + name + ".bytes"; object obj = BinarySerializeOpt.XmlDeserialize(xmlPath, type); BinarySerializeOpt.ProtoSerialize(protobufPath, obj); Debug.Log(name + "xml转protobuf成功,protobuf路径为:" + protobufPath); } } catch (Exception) {
Debug.LogError(name + "xml转protobuf失败"); } } }
|
ProtoBuf序列化准备
我们以MonsterData
为例,打开MonsterData
脚本
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| using System.Collections.Generic; using System.Xml.Serialization;
[ProtoBuf.ProtoContract] [System.Serializable] public class MonsterData : ExcelBase { #if UNITY_EDITOR public override void Construction() { } #endif public override void Init() { m_AllMonsterDic.Clear(); foreach (MonsterBase monster in AllMonster) { if (m_AllMonsterDic.ContainsKey(monster.Id)) { UnityEngine.Debug.LogError(monster.Name + " 有重复ID"); } else { m_AllMonsterDic.Add(monster.Id, monster); } } } public MonsterBase FindMonsterById(int id) { MonsterBase monster; if (m_AllMonsterDic.TryGetValue(id, out monster)) { return monster; } return null; } [XmlIgnore] public Dictionary<int, MonsterBase> m_AllMonsterDic = new Dictionary<int, MonsterBase>(); [ProtoBuf.ProtoMember(1)] [XmlElement("AllMonster")] public List<MonsterBase> AllMonster { get; set; } } [ProtoBuf.ProtoContract] [System.Serializable] public class MonsterBase { [ProtoBuf.ProtoMember(1)] [XmlAttribute("Id")] public int Id { get; set; } [ProtoBuf.ProtoMember(2)] [XmlAttribute("Name")] public string Name { get; set; } [ProtoBuf.ProtoMember(3)] [XmlAttribute("OutLook")] public string OutLook { get; set; } [ProtoBuf.ProtoMember(4)] [XmlAttribute("Level")] public int Level { get; set; } [ProtoBuf.ProtoMember(5)] [XmlAttribute("Rarity")] public int Rarity { get;set; } [ProtoBuf.ProtoMember(6)] [XmlAttribute("Height")] public float Height { get; set; } [ProtoBuf.ProtoMember(7)] [XmlElement("AllString")] public List<string> AllString { get; set; } [ProtoBuf.ProtoMember(8)] [XmlElement("AllBuff")] public List<BuffBase> AllBuff { get; set; } } [ProtoBuf.ProtoContract] [System.Serializable] public class BuffBase { [ProtoBuf.ProtoMember(1)] [XmlAttribute("Id")] public int Id { get; set; } [ProtoBuf.ProtoMember(2)] [XmlAttribute("Name")] public string Name { get; set; } }
|
Protobuf在使用时,给需要序列化的类添加ProtoBuf.ProtoContract
特性,给内部的变量添加ProtoBuf.ProtoMember(index)
特性
关于index数字的要求:
- 必须是正整数,(为了更好的可移植性,这个数必须
<=536870911
,并且不在19000-19999
范围内)
- 它们在单个类型中必须是唯一的,但如果启用继承,则相同的数字可以在子类型中重复使用
- 数字不得与任何继承Attribute中的数字冲突,(下面的Protobuf继承会讲到)
- 数字越小占用空间越少,尽量从1开始
- index数字是数据的唯一标记,我们可以随便修改变量属性的名字等。
测试
选中Assets——GameData——Data——Xml中的MonsterData.xml,然后“右键——Xml转Protobuf”
Protobuf继承
如果Protobuf需要序列化一个子类,需要在对应的基类设置ProtoInclude
特性
基类在使用这个特性时,ProtoInclude(int,Type)
数字参数不能与基类ProtoMember(int)
里面的数字重复
举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| [ProtoBuf.ProtoContract] [ProtoBuf.ProtoInclude(5,typeof(FireBuff))] [System.Serializable] public class BuffBase { [ProtoBuf.ProtoMember(1)] [XmlAttribute("Id")] public int Id { get; set; } [ProtoBuf.ProtoMember(2)] [XmlAttribute("Name")] public string Name { get; set; } } [ProtoBuf.ProtoContract] [System.Serializable] public class FireBuff : BuffBase { [ProtoBuf.ProtoMember(1)] [XmlAttribute("Hit")] public int Hit { get; set; } }
|