Unity|Unity C# 网络学习(十一)——自定义协议生成工具

Unity C# 网络学习(十一)——自定义协议生成工具 【Unity|Unity C# 网络学习(十一)——自定义协议生成工具】在开发网络游戏中,协议是必不可少的东西,一款游戏可能有非常多的协议,但是协议的重复性非常高,而且前端后端都需要,人工完成显然不现实,可以通过共同的配置去生成我们的协议
一.协议配置文件

  • 这里采用Xml来进行协议的配置
1 2

二.测试读取Xml配置文件
private void Start() { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(Application.dataPath + "/Src/Lesson32/Lesson32.xml"); XmlNode rootNode = xmlDocument.SelectSingleNode("messages"); if (rootNode == null) return; //读取枚举 XmlNodeList enumNodeList = rootNode.SelectNodes("enum"); if (enumNodeList == null) return; foreach (XmlNode enumNode in enumNodeList) { if (enumNode?.Attributes == null) continue; Debug.Log("枚举名称:" + enumNode.Attributes["name"].Value); Debug.Log("命名空间:" + enumNode.Attributes["namespace"].Value); XmlNodeList fieldNodeList = enumNode.SelectNodes("field"); if (fieldNodeList == null) continue; foreach (XmlNode fieldNode in fieldNodeList) { if (fieldNode?.Attributes == null) continue; Debug.Log(fieldNode.Attributes["name"].Value); if (!string.IsNullOrEmpty(fieldNode.InnerText)) Debug.Log(fieldNode.InnerText); } }//读取自定义Data类数据 XmlNodeList dataNodeList = rootNode.SelectNodes("data"); if (dataNodeList == null) return; foreach (XmlNode dataNode in dataNodeList) { if (dataNode?.Attributes == null) continue; Debug.Log("数据名称:" + dataNode.Attributes["name"].Value); Debug.Log("命名空间:" + dataNode.Attributes["namespace"].Value); XmlNodeList fieldNodeList = dataNode.SelectNodes("field"); if (fieldNodeList == null) continue; foreach (XmlNode fieldNode in fieldNodeList) { if (fieldNode?.Attributes == null) continue; Debug.Log(fieldNode.Attributes["name"].Value); Debug.Log(fieldNode.Attributes["type"].Value); } }//读取消息类型 XmlNodeList msgNodeList = rootNode.SelectNodes("message"); if (msgNodeList == null) return; foreach (XmlNode msgNode in msgNodeList) { if (msgNode?.Attributes == null) continue; Debug.Log("消息类名称:" + msgNode.Attributes["name"].Value); Debug.Log("消息ID:" + msgNode.Attributes["id"].Value); Debug.Log("命名空间:" + msgNode.Attributes["namespace"].Value); } }

三.根据Enum配置生成对应的C#代码
public void GenerateEnum(XmlNodeList nodeList) { if (nodeList == null) return; StringBuilder sbr = new StringBuilder(); foreach (XmlNode enumNode in nodeList) { sbr.Clear(); if (enumNode?.Attributes == null) continue; string className = enumNode.Attributes["name"].Value; string namespaceName = enumNode.Attributes["namespace"].Value; XmlNodeList fieldNodeList = enumNode.SelectNodes("field"); if (fieldNodeList == null) continue; sbr.Append($"namespace {namespaceName}\r\n"); sbr.Append("{\r\n"); sbr.Append($"\tpublic enum {className}\r\n"); sbr.Append("\t{\r\n"); foreach (XmlNode fieldNode in fieldNodeList) { if (fieldNode?.Attributes == null) continue; string fieldName = fieldNode.Attributes["name"].Value; sbr.Append("\t\t"); if (string.IsNullOrEmpty(fieldNode.InnerText)) sbr.Append(fieldName); else { sbr.Append(fieldName); sbr.Append(" = "); sbr.Append(fieldNode.InnerText); }sbr.Append(",\r\n"); }sbr.Append("\t}\r\n"); sbr.Append("}\r\n"); string path = SavePath + namespaceName + "/Enum"; if (!Directory.Exists(path)) Directory.CreateDirectory(path); using StreamWriter sw = new StreamWriter(path + "/" + className + ".cs"); sw.Write(sbr.ToString()); } }

四.根据Data配置生成数据类
public void GenerateData(XmlNodeList nodeList) { if (nodeList == null) return; StringBuilder sbr = new StringBuilder(); foreach (XmlNode dataNode in nodeList) { sbr.Clear(); if (dataNode?.Attributes == null) continue; string className = dataNode.Attributes["name"].Value; string namespaceName = dataNode.Attributes["namespace"].Value; XmlNodeList fieldNodeList = dataNode.SelectNodes("field"); if (fieldNodeList == null) continue; sbr.Append("using System.Collections.Generic; \r\n"); sbr.Append("using System.Text; \r\n"); sbr.Append($"namespace {namespaceName}\r\n"); sbr.Append("{\r\n"); sbr.Append($"\tpublic class {className} : BaseData\r\n"); sbr.Append("\t{\r\n"); foreach (XmlNode fieldNode in fieldNodeList) { if (fieldNode?.Attributes == null) continue; string fieldName = fieldNode.Attributes["name"].Value; string typeName = fieldNode.Attributes["type"].Value; sbr.Append("\t\tpublic "); if (typeName == "array") { string t = fieldNode.Attributes["T"].Value; sbr.Append($"{t}[] {fieldName}"); } else if (typeName == "list") { string t = fieldNode.Attributes["T"].Value; sbr.Append($"List<{t}> {fieldName}"); } else if (typeName == "dic") { string key = fieldNode.Attributes["Tkey"].Value; string val = fieldNode.Attributes["TValue"].Value; sbr.Append($"Dictionary<{key},{val}> {fieldName}"); } else if (typeName == "enum") { string t = fieldNode.Attributes["T"].Value; sbr.Append($"{t} {fieldName}"); } else { sbr.Append($"{typeName} {fieldName}"); }sbr.Append("; \r\n"); }sbr.Append("\r\n"); //开始写入函数相关 //1.获取字节长度函数 SetDataLength(sbr, fieldNodeList); sbr.Append("\r\n"); //2.ToArray()函数 SetToArray(sbr, fieldNodeList); sbr.Append("\r\n"); //3.Reading()函数 SetReading(sbr, fieldNodeList); sbr.Append("\r\n"); sbr.Append("\t}\r\n"); sbr.Append("}\r\n"); string path = SavePath + namespaceName + "/Data"; if (!Directory.Exists(path)) Directory.CreateDirectory(path); using StreamWriter sw = new StreamWriter(path + "/" + className + ".cs"); sw.Write(sbr.ToString()); } }

1.获取字节长度函数
private void SetDataLength(StringBuilder sbr, XmlNodeList fieldNodeList) { sbr.Append("\t\tpublic override int GetLength()\r\n"); sbr.Append("\t\t{\r\n"); sbr.Append("\t\t\tint num = 0; \r\n"); foreach (XmlNode fieldNode in fieldNodeList) { if (fieldNode?.Attributes == null) continue; string type = fieldNode.Attributes["type"].Value; string name = fieldNode.Attributes["name"].Value; sbr.Append("\t\t\t"); switch (type) { case "array": string arrT = fieldNode.Attributes["T"].Value; sbr.Append("num += 4; \r\n"); sbr.Append($"\t\t\tforeach ({arrT} t in {name})\r\n"); sbr.Append($"\t\t\t\tnum += {GetLength(arrT, "t")}; \r\n"); break; case "list": string listT = fieldNode.Attributes["T"].Value; sbr.Append("num += 4; \r\n"); sbr.Append($"\t\t\tforeach ({listT} t in {name})\r\n"); sbr.Append($"\t\t\t\tnum += {GetLength(listT, "t")}; \r\n"); break; case "dic": string keyT = fieldNode.Attributes["Tkey"].Value; string valueT = fieldNode.Attributes["TValue"].Value; sbr.Append("num += 4; \r\n"); sbr.Append($"\t\t\tforeach({keyT} key in {name}.Keys)\r\n"); sbr.Append("\t\t\t{\r\n"); sbr.Append($"\t\t\t\tnum += {GetLength(keyT, "key")}; \r\n"); sbr.Append($"\t\t\t\tnum += {GetLength(valueT, name + "[key]")}; \r\n"); sbr.Append("\t\t\t}\r\n"); break; default: sbr.Append( $"num += {GetLength(type, name)}; \r\n"); break; } }sbr.Append("\t\t\treturn num; \r\n"); sbr.Append("\t\t}\r\n"); }private string GetLength(string type, string name) { switch (type) { case "int": case "float": case "enum": return "4"; case "bool": case "byte": return "1"; case "long": return "8"; case "short": return "2"; case "string": return $"(4 + Encoding.UTF8.GetBytes({name}).Length)"; default: return $"{name}.GetLength()"; } }

2.ToArray()函数
private void SetToArray(StringBuilder sbr, XmlNodeList fieldNodeList) { sbr.Append("\t\tpublic override byte[] ToArray()\r\n"); sbr.Append("\t\t{\r\n"); sbr.Append("\t\t\tint index = 0; \r\n"); sbr.Append("\t\t\tbyte[] buffer = new byte[GetLength()]; \r\n"); foreach (XmlNode fieldNode in fieldNodeList) { if (fieldNode?.Attributes == null) continue; string name = fieldNode.Attributes["name"].Value; string type = fieldNode.Attributes["type"].Value; switch (type) { case "enum": sbr.Append($"\t\t\tWriteInt(buffer,(int){name},ref index); \r\n"); break; case "array": string arrT = fieldNode.Attributes["T"].Value; sbr.Append($"\t\t\tWriteInt(buffer,{name}.Length,ref index); \r\n"); sbr.Append($"\t\t\tforeach ({arrT} t in {name})\r\n"); sbr.Append($"\t\t\t\t{GetWriteFuncString(arrT, "t")}\r\n"); break; case "list": string listT = fieldNode.Attributes["T"].Value; sbr.Append($"\t\t\tWriteInt(buffer,{name}.Count,ref index); \r\n"); sbr.Append($"\t\t\tforeach ({listT} t in {name})\r\n"); sbr.Append($"\t\t\t\t{GetWriteFuncString(listT, "t")}\r\n"); break; case "dic": string keyT = fieldNode.Attributes["Tkey"].Value; string valueT = fieldNode.Attributes["TValue"].Value; sbr.Append($"\t\t\tWriteInt(buffer,{name}.Count,ref index); \r\n"); sbr.Append($"\t\t\tforeach ({keyT} key in {name}.Keys)\r\n"); sbr.Append("\t\t\t{\r\n"); sbr.Append($"\t\t\t\t{GetWriteFuncString(keyT, "key")}\r\n"); sbr.Append($"\t\t\t\t{GetWriteFuncString(valueT, name+"[key]")}\r\n"); sbr.Append("\t\t\t}\r\n"); break; default: sbr.Append($"\t\t\t{GetWriteFuncString(type, name)}\r\n"); break; } }sbr.Append("\t\t\treturn buffer; \r\n"); sbr.Append("\t\t}\r\n"); }private string GetWriteFuncString(string type,string name) { switch (type) { case "enum": return $"WriteInt(buffer,(int){name},ref index); "; case "int": return $"WriteInt(buffer,{name},ref index); "; case "float": return $"WriteFloat(buffer,{name},ref index); "; case "bool": return $"WriteBool(buffer,{name},ref index); "; case "long": return $"WriteLong(buffer,{name},ref index); "; case "string": return $"WriteString(buffer,{name},ref index); "; case "short": return $"WriteShort(buffer,{name},ref index); "; default: return $"WriteData(buffer,{name},ref index); "; } }

3.SetReading()函数
private void SetReading(StringBuilder sbr, XmlNodeList fieldNodeList) { sbr.Append("\t\tpublic override int Reading(byte[] bytes, int startIndex = 0)\r\n"); sbr.Append("\t\t{\r\n"); sbr.Append("\t\t\tint index = startIndex; \r\n"); foreach (XmlNode fieldNode in fieldNodeList) { if (fieldNode?.Attributes == null) continue; string name = fieldNode.Attributes["name"].Value; string type = fieldNode.Attributes["type"].Value; sbr.Append("\t\t\t"); switch (type) { case "enum": string enumT = fieldNode.Attributes["T"].Value; sbr.Append($"{name} = ({enumT})ReadInt(bytes, ref index); \r\n"); break; case "array": string arrayT = fieldNode.Attributes["T"].Value; sbr.Append($"{name} = new {arrayT}[ReadInt(bytes, ref index)]; \r\n"); sbr.Append($"\t\t\tfor (int i = 0; i < {name}.Length; i++)\r\n"); sbr.Append($"\t\t\t\t{name}[i] = {GetReadFuncString(arrayT)}; \r\n"); break; case "list": string listT = fieldNode.Attributes["T"].Value; sbr.Append($"{name} = new List<{listT}>(ReadInt(bytes, ref index)); \r\n"); sbr.Append($"\t\t\tfor (int i = 0; i < {name}.Capacity; i++)\r\n"); sbr.Append($"\t\t\t\t{name}.Add({GetReadFuncString(listT)}); \r\n"); break; case "dic": string keyT = fieldNode.Attributes["Tkey"].Value; string valueT = fieldNode.Attributes["TValue"].Value; sbr.Append($"int {name}Count = ReadInt(bytes, ref index); \r\n"); sbr.Append($"\t\t\t{name} = new Dictionary<{keyT},{valueT}>({name}Count); \r\n"); sbr.Append($"\t\t\tfor (int i = 0; i < {name}Count; i++)\r\n"); sbr.Append($"\t\t\t\t{name}[{GetReadFuncString(keyT)}] = {GetReadFuncString(valueT)}; \r\n"); break; default: sbr.Append($"{name} = {GetReadFuncString(type)}; \r\n"); break; } }sbr.Append("\t\t\treturn index - startIndex; \r\n"); sbr.Append("\t\t}\r\n"); }private string GetReadFuncString(string type) { switch (type) { case "int": return "ReadInt(bytes, ref index)"; case "float": return "ReadFloat(bytes, ref index)"; case "bool": return "ReadBool(bytes, ref index)"; case "long": return "ReadLong(bytes, ref index)"; case "string": return "ReadString(bytes, ref index)"; case "short": return "ReadShort(bytes, ref index)"; default: return $"ReadData<{type}>(bytes, ref index)"; } }

五.根据Msg配置生成对应代码
  • 与上面的根据Data配置生成数据类类似
  • 修改继承的基类为MsgBase
  • 修改GetLength()方法,让最小长度为8
  • 修改ToArray()方法,写入消息ID和消息长度
  • 新增返回MsgID的方法
六.测试
private void Start() { GamePlayer.PlayerData playerData = https://www.it610.com/article/new GamePlayer.PlayerData { id = 1000, atk = 41.45f, sex = false, lev = 6666666666, playerType = E_PLAYER_TYPE.OTHER, arrays = new[] {1, 2, 3, 4, 5, 6}, list = new List {1, 3, 5, 7, 9}, dic = new Dictionary { {1, "zzs"}, {2, "ywj"} }, hp = 100, itemList = new List{"zzs","wy","lzq"}, name = "zzs", info = "这是测试", monsterDic = new Dictionary { {"zzs","ywj"} }, homeArr = new []{3.14f,6.6789f}, moneyData = https://www.it610.com/article/new MoneyData { money = 99999, moneyArr = new []{1,2,3}, moneyDic = new Dictionary{{"zzs",1000}}, moneyList = new List{1,2,3,4} } }; GamePlayer.PlayerMsg playerMsg = new GamePlayer.PlayerMsg { playerID = 100001, data =https://www.it610.com/article/playerData }; byte[] buffer = playerMsg.ToArray(); int index = 0; int msgId = BitConverter.ToInt32(buffer,index); index += 4; int length = BitConverter.ToInt32(buffer, index); index += 4; GamePlayer.PlayerMsg playerMsg1 = new GamePlayer.PlayerMsg(); playerMsg1.Reading(buffer, index); }

    推荐阅读