Unity C# 网络学习(十一)——自定义协议生成工具
在开发网络游戏中,协议是必不可少的东西,一款游戏可能有非常多的协议,但是协议的重复性非常高,而且前端后端都需要,人工完成显然不现实,可以通过共同的配置去生成我们的协议
一.协议配置文件
- 这里采用Xml来进行协议的配置
<?xml version="1.0" encoding="utf-8"?>
<messages>
<!--枚举类型-->
<enum name="E_PLAYER_TYPE" namespace="GamePlayer">
<field name="MAIN">1</field>
<field name="OTHER"/>
</enum>
<enum name="E_MONSTER_TYPE" namespace="GameMonster">
<field name="NORMAL">2</field>
<field name="BOSS"/>
</enum>
<!--数据结构类型-->
<data name="PlayerData" namespace="GamePlayer">
<field name="id" type="int"/>
<field name="atk" type="float"/>
<field name="sex" type="bool"/>
<field name="lev" type="long"/>
<field name="arrays" type="array" T="int"/>
<field name="list" type="list" T="int"/>
<field name="dic" type="dic" Tkey="int" TValue="string"/>
</data>
<!--消息类型-->
<message name="PlayerMsg" id="1001" namespace="GamePlayer">
<field name="playerID" type="int" />
<field name="data" type="PlayerData"/>
</message>
<message name="HeartMsg" id="1002" namespace="GameSystem"/>
</messages>
二.测试读取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 = 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<int> {
1, 3, 5, 7, 9},
dic = new Dictionary<int, string>
{
{
1, "zzs"},
{
2, "ywj"}
},
hp = 100,
itemList = new List<string>{
"zzs","wy","lzq"},
name = "zzs",
info = "这是测试",
monsterDic = new Dictionary<string, string>
{
{
"zzs","ywj"}
},
homeArr = new []{
3.14f,6.6789f},
moneyData = new MoneyData
{
money = 99999,
moneyArr = new []{
1,2,3},
moneyDic = new Dictionary<string, int>{
{
"zzs",1000}},
moneyList = new List<int>{
1,2,3,4}
}
};
GamePlayer.PlayerMsg playerMsg = new GamePlayer.PlayerMsg
{
playerID = 100001,
data = 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);
}