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);
}
推荐阅读
- Unity3d-2019.4-简易红绿灯
- unity|Unity3D协程详解
- C#|C# Any()和AII()方法
- 前端|前端求职难(那是你没看见这个)
- Unity功能|Unity之UGUI-特效遮挡问题2.0
- #|向上转型和向下转型
- 经验之谈|C#中using关键字的作用及其用法
- Using在C#中的关键作用
- .Net|.Net C# Using 关键字