UN5L3——协议(消息)生成
UN5L3——协议(消息)生成
协议(消息)生成主要做什么
协议生成 主要是使用配置文件中读取出来的信息,动态的生成对应语言的代码文件
每次添加消息或者数据结构类时,我们不需要再手写代码了
我们不仅可以生成C#脚本文件,还可以根据需求生成别的语言的文件
根据配置生成脚本的文件的主要思路就是,按规则拼接字符串
只要有数据和规则,我们就可以动态的创建脚本文件
制作功能前的准备工作
协议生成是不会在发布后使用的功能,主要是在开发时使用,所以我们在Unity当中可以把它作为一个编辑器功能来做
因此我们可以专门新建一个Editor文件夹(专门放编辑器相关内容,不会发布),在其中放置配置文件、自动生成相关脚本文件
创建一个生成协议(消息)脚本的工具类,并利用[MenuItem()]创建选项
1 | using UnityEditor; |
由于创建脚本代码逻辑较多,因此新声明一个类GenerateCSharp
用来实现脚本代码生成,由ProtocolTool
类调用
在GenerateCSharp
实现三个方法分别用来生成枚举脚本,数据类脚本,消息类脚本,这些方法需要传入对应的XML节点
1 | using System.IO; |
因此,在ProtocolTool
中实现读取指定名字子节点List的方法,再传入到GenerateCSharp
的方法内
1 | using System.Xml; |
制作生成枚举功能
-
读取xml枚举相关信息
遍历传入的枚举节点,读取其名字与命名空间名,再遍历枚举节点下的每个字段节点,读取其字段名与默认值
其中,字段的字符串可以提前拼接好,添加合适缩进和换行(使用\r\n
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public void GenerateEnum(XmlNodeList nodes)
{
//生成枚举脚本的逻辑
string namespaceStr = "";
string enumNameStr = "";
string fieldStr = "";
//遍历所有的枚举节点
foreach (XmlNode enumNode in nodes)
{
namespaceStr = enumNode.Attributes["namespace"].Value; //获取命名空间
enumNameStr = enumNode.Attributes["name"].Value; //获取枚举名
//遍历枚举下的所有字段消息
XmlNodeList enumFields = enumNode.SelectNodes("field");
fieldStr = ""; //新枚举需要清空上一次拼接的字段字符串
foreach (XmlNode fieldNode in enumFields)
{
fieldStr += "\t\t" + fieldNode.Attributes["name"].Value; //添加字段名
if (fieldNode.InnerText != "")
fieldStr += " = " + fieldNode.InnerText; //如果有值添加值
fieldStr += ",\r\n"; //换行
}
}
} -
根据枚举相关信息 拼接字符串
得到所有可变消息后,按照C#的代码格式,添加合适的关键词,缩进,换行,将所有可变内容拼接起来得到完整的代码字符串
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
31public void GenerateEnum(XmlNodeList nodes)
{
//生成枚举脚本的逻辑
string namespaceStr = "";
string enumNameStr = "";
string fieldStr = "";
//遍历所有的枚举节点
foreach (XmlNode enumNode in nodes)
{
namespaceStr = enumNode.Attributes["namespace"].Value; //获取命名空间
enumNameStr = enumNode.Attributes["name"].Value; //获取枚举名
//遍历枚举下的所有字段消息
XmlNodeList enumFields = enumNode.SelectNodes("field");
fieldStr = ""; //新枚举需要清空上一次拼接的字段字符串
foreach (XmlNode fieldNode in enumFields)
{
fieldStr += "\t\t" + fieldNode.Attributes["name"].Value; //添加字段名
if (fieldNode.InnerText != "")
fieldStr += " = " + fieldNode.InnerText; //如果有值添加值
fieldStr += ",\r\n"; //换行
}
//对所有的可变的内容进行拼接
string enumStr = $"namespace {namespaceStr}\r\n" + //命名空间
"{\r\n" +
$"\tpublic enum {enumNameStr}\r\n" + //枚举声明
"\t{\r\n" +
$"{fieldStr}" + //添加字段字符串
"\t}\r\n" +
"}";
}
} -
生成枚举脚本文件
将拼接得到的代码字符串保存到指定路径下,每个命名空间一个文件夹,所有同一命名空间下的枚举脚本放一个文件夹
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//协议保存路径
private string SAVE_PATH = Application.dataPath + "/Scripts/Protocol/";
public void GenerateEnum(XmlNodeList nodes)
{
//生成枚举脚本的逻辑
string namespaceStr = "";
string enumNameStr = "";
string fieldStr = "";
//遍历所有的枚举节点
foreach (XmlNode enumNode in nodes)
{
namespaceStr = enumNode.Attributes["namespace"].Value; //获取命名空间
enumNameStr = enumNode.Attributes["name"].Value; //获取枚举名
//遍历枚举下的所有字段消息
XmlNodeList enumFields = enumNode.SelectNodes("field");
fieldStr = ""; //新枚举需要清空上一次拼接的字段字符串
foreach (XmlNode fieldNode in enumFields)
{
fieldStr += "\t\t" + fieldNode.Attributes["name"].Value; //添加字段名
if (fieldNode.InnerText != "")
fieldStr += " = " + fieldNode.InnerText; //如果有值添加值
fieldStr += ",\r\n"; //换行
}
//对所有的可变的内容进行拼接
string enumStr = $"namespace {namespaceStr}\r\n" + //命名空间
"{\r\n" +
$"\tpublic enum {enumNameStr}\r\n" + //枚举声明
"\t{\r\n" +
$"{fieldStr}" + //添加字段字符串
"\t}\r\n" +
"}";
//保存文件的路径
string path = SAVE_PATH + namespaceStr + "/Enum/";
//若不存在就创建
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
//字符串保存 存储为枚举脚本文件
File.WriteAllText(path + enumNameStr + ".cs", enumStr);
}
Debug.Log("枚举生成结束");
}
制作生成数据类功能
分析制作数据结构类的构成
数据结构的类的全部内容如下
1 | using System.Text; |
通过分析可知,我们需要根据字段生成成员变量声明,
再根据各个成员变量的长度生成GetBytesNum()
,根据各个成员变量的类型生成Reading()
和Writeing()
制作生成数据结构类步骤
声明一个导入所有<data>
节点生成数据结构类的方法,在ProtocolTool
中调用
1 | [ ] |
-
生成成员变量声明
假设我们要生成这样的类声明和成员变量声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14namespace GamePlayer
{
public class PlayerData : BaseData
{
public int id;
public float atk;
public bool sex;
public long lev;
public int[] arrays;
public List<int> list;
public Dictionary<int, string> dic;
public E_HERO_TYPE heroType;
}
}遍历传入的所有
<data>
节点,读取命名及所在命名空间,完成类的声明,然后在类的声明中先声明各个变量
数据类变量的声明是可变的,因此使用一个单独的字符串变量装载,该字符串变量包括所有的变量声明语句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
39public void GenerateData(XmlNodeList nodes)
{
string namespaceStr = "";
string classNameStr = "";
string fieldStr = "";
foreach (XmlNode dataNode in nodes)
{
namespaceStr = dataNode.Attributes["namespace"].Value;
classNameStr = dataNode.Attributes["name"].Value;
XmlNodeList fields = dataNode.SelectNodes("field"); //获取所有的字段消息
fieldStr = GetFieldStr(fields); //根据字段获取变量声明相关字符串
string dataStr = $"using System;\r\n" +
$"using System.Collections.Generic;\r\n" +
$"using System.Text;\r\n" +
$"using UnityEngine;\r\n\r\n" +
//命名空间
$"namespace {namespaceStr}\r\n" +
"{\r\n" +
//类声明
$"\tpublic class {classNameStr} : BaseData\r\n" +
"\t{\r\n" +
//拼接各个变量声明的字符串
$"{fieldStr}\r\n" +
//TODO.. GetBytesNum声明
//TODO.. Writing声明
//TODO.. Reading声明
"\t}\r\n" +
"}";
//保存为 脚本文件
//保存文件的路径
string path = SAVE_PATH + namespaceStr + "/Data/";
//若不存在就创建
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
File.WriteAllText(path + classNameStr + ".cs", dataStr);
}
Debug.Log("数据结构类生成结束");
}生成声明各个变量的语句逻辑较为复杂,因此另外声明一个方法
GetFieldStr
,
该方法需要导入<data>
节点下的所有的<field>
节点,遍历解析,生成所有数据类变量的声明
其中list
、array
、dic
、enum
类型,它们需要另外识别类型,因此需要读取特定的属性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//拼接各个成员变量的方法
private string GetFieldStr(XmlNodeList fields)
{
string fieldStr = "";
//遍历所有的字段节点,生成对应的遍历声明语句
foreach (XmlNode field in fields)
{
string type = field.Attributes["type"].Value; //变量类型
string fieldName = field.Attributes["name"].Value; //变量名
//列表声明语句
if (type == "list")
{
string T = field.Attributes["T"].Value;
fieldStr += $"\t\tpublic List<{T}> ";
}
//数组声明语句
else if (type == "array")
{
string data = field.Attributes["data"].Value;
fieldStr += $"\t\tpublic {data}[] ";
}
//字典声明语句
else if (type == "dic")
{
string TKey = field.Attributes["TKey"].Value;
string TValue = field.Attributes["TValue"].Value;
fieldStr += $"\t\tpublic Dictionary<{TKey}, {TValue}> ";
}
//枚举声明语句
else if (type == "enum")
{
string data = field.Attributes["data"].Value;
fieldStr += $"\t\tpublic {data} ";
}
//变量声明语句
else
{
fieldStr += $"\t\tpublic {type} ";
}
fieldStr += fieldName + ";\r\n";
}
return fieldStr;
} -
生成
GetBytesNum
获取字节数函数假设我们要生成这样的
GetBytesNum
方法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
37namespace GamePlayer
{
public class PlayerData : BaseData
{
public int id;
public float atk;
public bool sex;
public long lev;
public int[] arrays;
public List<int> list;
public Dictionary<int, string> dic;
public E_HERO_TYPE heroType;
public override int GetBytesNum()
{
int num = 0;
num += 4; // id : sizeof(int)
num += 4; // atk : sizeof(float)
num += 1; // sex : sizeof(bool)
num += 8; // lev : sizeof(long)
num += 2; // arrays.Length : sizeof(short)
for (int i = 0; i < arrays.Length; ++i)
num += 4; // arrays : sizeof(int)
num += 2; // list.Count : sizeof(short)
for (int i = 0; i < list.Count; ++i)
num += 4; // list : sizeof(int)
num += 2; // dic.Count : sizeof(short)
foreach (int key in dic.Keys)
{
num += 4; // dic : sizeof(int, string)
num += 4 + Encoding.UTF8.GetByteCount(dic[key]);
}
num += 4; // heroType : sizeof(enum)
return num;
}
}
}
GetBytesNum
的声明语句是可变的,因此将其可变部分使用一个单独的字符串变量装载,该字符串变量包括所有可变的GetBytesNum
方法声明语句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
48public void GenerateData(XmlNodeList nodes)
{
string namespaceStr = "";
string classNameStr = "";
string fieldStr = "";
string getBytesNumStr = "";
foreach (XmlNode dataNode in nodes)
{
namespaceStr = dataNode.Attributes["namespace"].Value;
classNameStr = dataNode.Attributes["name"].Value;
XmlNodeList fields = dataNode.SelectNodes("field"); //获取所有的字段消息
fieldStr = GetFieldStr(fields); //根据字段获取变量声明相关字符串
//根据所有字段,对GetBytesNum函数中的字符串内容进行拼接 返回结果
getBytesNumStr = GetGetBytesNumStr(fields);
string dataStr = $"using System;\r\n" +
$"using System.Collections.Generic;\r\n" +
$"using System.Text;\r\n" +
$"using UnityEngine;\r\n\r\n" +
//命名空间
$"namespace {namespaceStr}\r\n" +
"{\r\n" +
//类声明
$"\tpublic class {classNameStr} : BaseData\r\n" +
"\t{\r\n" +
//拼接各个变量声明的字符串
$"{fieldStr}\r\n" +
//GetBytesNum声明
$"\t\tpublic override int GetBytesNum()\r\n" +
"\t\t{\r\n" +
"\t\t\tint num = 0;\r\n" +
$"{getBytesNumStr}" +
"\t\t\treturn num;\r\n" +
"\t\t}\r\n\r\n" +
//TODO.. Writing声明
//TODO.. Reading声明
"\t}\r\n" +
"}";
//保存为 脚本文件
//保存文件的路径
string path = SAVE_PATH + namespaceStr + "/Data/";
//若不存在就创建
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
File.WriteAllText(path + classNameStr + ".cs", dataStr);
}
Debug.Log("数据结构类生成结束");
}生成
GetBytesNum
的语句逻辑较为复杂,因此另外声明一个方法GetGetBytesNumStr
该方法需要遍历所有的字段,识别类型,根据字段的类型获取对应的字节数,再将生成字节数累加语句,
最终使得生成的GetBytesNum
方法可以获取消息类所有成员变量的总长度其中,基础类型包括枚举的变量的字节数是固定的,因此可以直接再另外声明
GetValueBytesNum
用于获取各种基本类型的字节数而
list
、array
、dic
的字节数是可变的,我们需要像字符串那样,
先+2
用来存储它们元素的数量,在根据list
、array
、dic
的类型来循环累加字节数
这样反序列化可以前通过前两个字节来确认元素数量,再根据数量循环反序列化各个元素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//拼接GetBytesNum函数的方法
private string GetGetBytesNumStr(XmlNodeList fields)
{
string bytesNumStr = "";
string type = "";
string name = "";
foreach (XmlNode field in fields)
{
type = field.Attributes["type"].Value;
name = field.Attributes["name"].Value;
if (type == "list")
{
string T = field.Attributes["T"].Value;
//+2 是为了节约字节数,用一个short去存储消息
bytesNumStr += $"\t\t\tnum += 2;\t\t// {name}.Count : sizeof(short)\r\n";
bytesNumStr += $"\t\t\tfor (int i = 0; i < {name}.Count; ++i)\r\n";
//这里使用的是 name + [i] 目的是获取list当中的元素传入进行使用
bytesNumStr += $"\t\t\t\tnum += {GetValueBytesNum(T, $"{name}[i]")};\t// {name} : sizeof({T})\r\n";
}
else if (type == "array")
{
string data = field.Attributes["data"].Value;
//+2 是为了节约字节数,用一个short去存储消息
bytesNumStr += $"\t\t\tnum += 2;\t\t// {name}.Length : sizeof(short)\r\n";
bytesNumStr += $"\t\t\tfor (int i = 0; i < {name}.Length; ++i)\r\n";
//这里使用的是 name + [i] 目的是获取list当中的元素传入进行使用
bytesNumStr += $"\t\t\t\tnum += {GetValueBytesNum(data, $"{name}[i]")};\t// {name} : sizeof({data})\r\n";
}
else if (type == "dic")
{
string TKey = field.Attributes["TKey"].Value;
string TValue = field.Attributes["TValue"].Value;
//+2 是为了节约字节数,用一个short去存储消息
bytesNumStr += $"\t\t\tnum += 2;\t\t// {name}.Count : sizeof(short)\r\n";
bytesNumStr += $"\t\t\tforeach ({TKey} key in {name}.Keys)\r\n";
bytesNumStr += "\t\t\t{\r\n";
bytesNumStr += $"\t\t\t\tnum += {GetValueBytesNum(TKey, $"key")};\t// {name} : sizeof({TKey}, {TValue})\r\n";
bytesNumStr += $"\t\t\t\tnum += {GetValueBytesNum(TValue, $"{name}[key]")};\r\n";
bytesNumStr += "\t\t\t}\r\n";
}
else
bytesNumStr += $"\t\t\tnum += {GetValueBytesNum(type, name)};\t\t// {name} : sizeof({type})\r\n";
}
return bytesNumStr;
}
private string GetValueBytesNum(string type, string name)
{
switch (type)
{
case "int":
case "float":
case "enum":
return "4";
case "byte":
case "bool":
return "1";
case "short":
return "2";
case "long":
case "double":
return "8";
case "string":
return $"4 + Encoding.UTF8.GetByteCount({name})";
default:
return $"{name}.GetBytesNum()";
}
} -
生成
Writing
序列化函数假设我们要生成这样的
Writing
序列化函数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
61namespace GamePlayer
{
public class PlayerData : BaseData
{
public int id;
public float atk;
public bool sex;
public long lev;
public int[] arrays;
public List<int> list;
public Dictionary<int, string> dic;
public E_HERO_TYPE heroType;
public override int GetBytesNum()
{
int num = 0;
num += 4; // id : sizeof(int)
num += 4; // atk : sizeof(float)
num += 1; // sex : sizeof(bool)
num += 8; // lev : sizeof(long)
num += 2; // arrays.Length : sizeof(short)
for (int i = 0; i < arrays.Length; ++i)
num += 4; // arrays : sizeof(int)
num += 2; // list.Count : sizeof(short)
for (int i = 0; i < list.Count; ++i)
num += 4; // list : sizeof(int)
num += 2; // dic.Count : sizeof(short)
foreach (int key in dic.Keys)
{
num += 4; // dic : sizeof(int, string)
num += 4 + Encoding.UTF8.GetByteCount(dic[key]);
}
num += 4; // heroType : sizeof(enum)
return num;
}
public override byte[] Writing()
{
int index = 0;
byte[] bytes = new byte[GetBytesNum()];
WriteInt(bytes, id, ref index);
WriteFloat(bytes, atk, ref index);
WriteBool(bytes, sex, ref index);
WriteLong(bytes, lev, ref index);
WriteShort(bytes, (short)arrays.Length, ref index);
for (int i = 0; i < arrays.Length; ++i)
WriteInt(bytes, arrays[i], ref index);
WriteShort(bytes, (short)list.Count, ref index);
for (int i = 0; i < list.Count; ++i)
WriteInt(bytes, list[i], ref index);
WriteShort(bytes, (short)dic.Count, ref index);
foreach (int key in dic.Keys)
{
WriteInt(bytes, key, ref index);
WriteString(bytes, dic[key], ref index);
}
WriteInt(bytes, Convert.ToInt32(heroType), ref index);
return bytes;
}
}
}
Writing
的声明语句也是可变的,因此将其可变部分使用一个单独的字符串变量装载,该字符串变量包括所有可变的Writing
方法声明语句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
58public void GenerateData(XmlNodeList nodes)
{
string namespaceStr = "";
string classNameStr = "";
string fieldStr = "";
string getBytesNumStr = "";
string writingStr = "";
foreach (XmlNode dataNode in nodes)
{
namespaceStr = dataNode.Attributes["namespace"].Value;
classNameStr = dataNode.Attributes["name"].Value;
XmlNodeList fields = dataNode.SelectNodes("field"); //获取所有的字段消息
fieldStr = GetFieldStr(fields); //根据字段获取变量声明相关字符串
//根据所有字段,对GetBytesNum函数中的字符串内容进行拼接 返回结果
getBytesNumStr = GetGetBytesNumStr(fields);
//通过所有字段,对Writing函数中的字符串内容进行拼接 返回结果
writingStr = GetWritingStr(fields);
string dataStr = $"using System;\r\n" +
$"using System.Collections.Generic;\r\n" +
$"using System.Text;\r\n" +
$"using UnityEngine;\r\n\r\n" +
//命名空间
$"namespace {namespaceStr}\r\n" +
"{\r\n" +
//类声明
$"\tpublic class {classNameStr} : BaseData\r\n" +
"\t{\r\n" +
//拼接各个变量声明的字符串
$"{fieldStr}\r\n" +
//GetBytesNum声明
$"\t\tpublic override int GetBytesNum()\r\n" +
"\t\t{\r\n" +
"\t\t\tint num = 0;\r\n" +
$"{getBytesNumStr}" +
"\t\t\treturn num;\r\n" +
"\t\t}\r\n\r\n" +
//Writing声明
$"\t\tpublic override byte[] Writing()\r\n" +
"\t\t{\r\n" +
"\t\t\tint index = 0;\r\n" +
$"\t\t\tbyte[] bytes = new byte[GetBytesNum()];\r\n" +
$"{writingStr}" +
"\t\t\treturn bytes;\r\n" +
"\t\t}\r\n\r\n" +
//TODO.. Reading声明
"\t}\r\n" +
"}";
//保存为 脚本文件
//保存文件的路径
string path = SAVE_PATH + namespaceStr + "/Data/";
//若不存在就创建
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
File.WriteAllText(path + classNameStr + ".cs", dataStr);
}
Debug.Log("数据结构类生成结束");
}生成
Writing
的语句逻辑较为复杂,因此另外声明一个方法GetWritingStr
该方法需要遍历所有的字段,识别类型,根据字段的类型,生成对应的序列化语句
最终使得生成的Writing
方法可以将消息类对象自己进行序列化由于已经封装了基础类型变量的序列化方法,可以直接调用,因此再声明
GetFieldWritingStr
来生成调用序列化基础类型变量方法的字符串
list
、array
、dic
的序列化语句是可变的,我们需要像字符串那样,
先生成序列化它们的元素的数量Short
的语句,再生成循环序列化其元素的语句,
这样反序列化就会先得到数量,在根据数量反序列化多少个元素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//拼接Writing函数的方法
private string GetWritingStr(XmlNodeList fields)
{
string writingStr = "";
string type = "";
string name = "";
foreach (XmlNode field in fields)
{
type = field.Attributes["type"].Value;
name = field.Attributes["name"].Value;
if (type == "list")
{
string T = field.Attributes["T"].Value;
writingStr += $"\t\t\tWriteShort(bytes, (short){name}.Count, ref index);\r\n";
writingStr += $"\t\t\tfor (int i = 0; i < {name}.Count; ++i)\r\n";
writingStr += $"\t\t\t\t{GetFieldWritingStr(T, $"{name}[i]")}\r\n";
}
else if (type == "array")
{
string data = field.Attributes["data"].Value;
writingStr += $"\t\t\tWriteShort(bytes, (short){name}.Length, ref index);\r\n";
writingStr += $"\t\t\tfor (int i = 0; i < {name}.Length; ++i)\r\n";
writingStr += $"\t\t\t\t{GetFieldWritingStr(data, $"{name}[i]")}\r\n";
}
else if (type == "dic")
{
string TKey = field.Attributes["TKey"].Value;
string TValue = field.Attributes["TValue"].Value;
writingStr += $"\t\t\tWriteShort(bytes, (short){name}.Count, ref index);\r\n";
writingStr += $"\t\t\tforeach ({TKey} key in {name}.Keys)\r\n";
writingStr += "\t\t\t{\r\n";
writingStr += $"\t\t\t\t{GetFieldWritingStr(TKey, "key")}\r\n";
writingStr += $"\t\t\t\t{GetFieldWritingStr(TValue, $"{name}[key]")}\r\n";
writingStr += "\t\t\t}\r\n";
}
else
{
writingStr += $"\t\t\t{GetFieldWritingStr(type, name)}\r\n";
}
}
return writingStr;
}
//获取各种基本类型的序列化语句字符串
private string GetFieldWritingStr(string type, string name)
{
switch (type)
{
case "byte":
return $"WriteByte(bytes, {name}, ref index);";
case "int":
return $"WriteInt(bytes, {name}, ref index);";
case "enum":
return $"WriteInt(bytes, Convert.ToInt32({name}), ref index);";
case "short":
return $"WriteShort(bytes, {name}, ref index);";
case "long":
return $"WriteLong(bytes, {name}, ref index);";
case "float":
return $"WriteFloat(bytes, {name}, ref index);";
case "double":
return $"WriteDouble(bytes, {name}, ref index);";
case "bool":
return $"WriteBool(bytes, {name}, ref index);";
case "string":
return $"WriteString(bytes, {name}, ref index);";
default:
return $"WriteData(bytes, {name}, ref index);";
}
} -
生成
Reading
反序列化函数假设我们要生成这样的
Reading
反序列化函数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
62namespace GamePlayer
{
public class PlayerData : BaseData
{
public int id;
public float atk;
public bool sex;
public long lev;
public int[] arrays;
public List<int> list;
public Dictionary<int, string> dic;
public E_HERO_TYPE heroType;
public override int GetBytesNum()
{
int num = 0;
num += 4; // id : sizeof(int)
num += 4; // atk : sizeof(float)
num += 1; // sex : sizeof(bool)
num += 8; // lev : sizeof(long)
num += 2; // arrays.Length : sizeof(short)
for (int i = 0; i < arrays.Length; ++i)
num += 4; // arrays : sizeof(int)
num += 2; // list.Count : sizeof(short)
for (int i = 0; i < list.Count; ++i)
num += 4; // list : sizeof(int)
num += 2; // dic.Count : sizeof(short)
foreach (int key in dic.Keys)
{
num += 4; // dic : sizeof(int, string)
num += 4 + Encoding.UTF8.GetByteCount(dic[key]);
}
num += 4; // heroType : sizeof(enum)
return num;
}
//省略Writing
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex;
id = ReadInt(bytes, ref index);
atk = ReadFloat(bytes, ref index);
sex = ReadBool(bytes, ref index);
lev = ReadLong(bytes, ref index);
short arraysLength = ReadShort(bytes, ref index);
arrays = new int[arraysLength];
for (int i = 0; i < arraysLength; ++i)
arrays[i] = ReadInt(bytes, ref index);
list = new List<int>();
short listCount = ReadShort(bytes, ref index);
for (int i = 0; i < listCount; ++i)
list.Add(ReadInt(bytes, ref index));
dic = new Dictionary<int, string>();
short dicCount = ReadShort(bytes, ref index);
for (int i = 0; i < dicCount; ++i)
dic.Add(ReadInt(bytes, ref index), ReadString(bytes, ref index));
heroType = (E_HERO_TYPE)ReadInt(bytes, ref index);
return index - beginIndex;
}
}
}
Reading
的声明语句也是可变的,因此将其可变部分使用一个单独的字符串变量装载,该字符串变量包括所有可变的Reading
方法声明语句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
67public void GenerateData(XmlNodeList nodes)
{
string namespaceStr = "";
string classNameStr = "";
string fieldStr = "";
string getBytesNumStr = "";
string writingStr = "";
string readingStr = "";
foreach (XmlNode dataNode in nodes)
{
namespaceStr = dataNode.Attributes["namespace"].Value;
classNameStr = dataNode.Attributes["name"].Value;
XmlNodeList fields = dataNode.SelectNodes("field"); //获取所有的字段消息
fieldStr = GetFieldStr(fields); //根据字段获取变量声明相关字符串
//根据所有字段,对GetBytesNum函数中的字符串内容进行拼接 返回结果
getBytesNumStr = GetGetBytesNumStr(fields);
//根据所有字段,对Writing函数中的字符串内容进行拼接 返回结果
writingStr = GetWritingStr(fields);
//根据所有字段,对Reading函数中的字符串内容进行拼接 返回结果
readingStr = GetReadingStr(fields);
string dataStr = $"using System;\r\n" +
$"using System.Collections.Generic;\r\n" +
$"using System.Text;\r\n" +
$"using UnityEngine;\r\n\r\n" +
//命名空间
$"namespace {namespaceStr}\r\n" +
"{\r\n" +
//类声明
$"\tpublic class {classNameStr} : BaseData\r\n" +
"\t{\r\n" +
//拼接各个变量声明的字符串
$"{fieldStr}\r\n" +
//GetBytesNum声明
$"\t\tpublic override int GetBytesNum()\r\n" +
"\t\t{\r\n" +
"\t\t\tint num = 0;\r\n" +
$"{getBytesNumStr}" +
"\t\t\treturn num;\r\n" +
"\t\t}\r\n\r\n" +
//Writing声明
$"\t\tpublic override byte[] Writing()\r\n" +
"\t\t{\r\n" +
"\t\t\tint index = 0;\r\n" +
$"\t\t\tbyte[] bytes = new byte[GetBytesNum()];\r\n" +
$"{writingStr}" +
"\t\t\treturn bytes;\r\n" +
"\t\t}\r\n\r\n" +
//Reading声明
$"\t\tpublic override int Reading(byte[] bytes, int beginIndex = 0)\r\n" +
"\t\t{\r\n" +
"\t\t\tint index = beginIndex;\r\n" +
$"{readingStr}" +
"\t\t\treturn index - beginIndex;\r\n" +
"\t\t}\r\n" +
"\t}\r\n" +
"}";
//保存为 脚本文件
//保存文件的路径
string path = SAVE_PATH + namespaceStr + "/Data/";
//若不存在就创建
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
File.WriteAllText(path + classNameStr + ".cs", dataStr);
}
Debug.Log("数据结构类生成结束");
}生成
Reading
的语句逻辑较为复杂,因此另外声明一个方法GetReadingStr
该方法需要遍历所有的字段,识别类型,根据字段的类型,生成对应的反序列化语句
最终使得生成的Reading
方法可以从字节数组中反序列化对象由于已经封装了基础类型变量的反序列化方法,可以直接调用,因此再声明
GetFieldReadingStr
来生成调用反序列化基础类型变量方法的字符串其中
enum
的反序列化实际上是反序列化一个int
值,再强转为对应枚举
list
、array
、dic
的反序列化语句是可变的,我们需要像字符串那样,
先生成反序列化它们的元素的数量Short
的语句,再生成循环反序列化元素的语句,
这样反序列化就会先得到数量,在根据数量反序列化多少个元素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//拼接Reading函数的方法
private string GetReadingStr(XmlNodeList fields)
{
string readingStr = "";
string type = "";
string name = "";
foreach (XmlNode field in fields)
{
type = field.Attributes["type"].Value;
name = field.Attributes["name"].Value;
if (type == "list")
{
string T = field.Attributes["T"].Value;
readingStr += $"\t\t\t{name} = new List<{T}>();\r\n";
readingStr += $"\t\t\tshort {name}Count = ReadShort(bytes, ref index);\r\n";
readingStr += $"\t\t\tfor (int i = 0; i < {name}Count; ++i)\r\n";
readingStr += $"\t\t\t\t{name}.Add({GetFieldReadingStr(T)});\r\n";
}
else if (type == "array")
{
string data = field.Attributes["data"].Value;
readingStr += $"\t\t\tshort {name}Length = ReadShort(bytes, ref index);\r\n";
readingStr += $"\t\t\t{name} = new {data}[{name}Length];\r\n";
readingStr += $"\t\t\tfor (int i = 0; i < {name}Length; ++i)\r\n";
readingStr += $"\t\t\t\t{name}[i] = {GetFieldReadingStr(data)};\r\n";
}
else if (type == "dic")
{
string TKey = field.Attributes["TKey"].Value;
string TValue = field.Attributes["TValue"].Value;
readingStr += $"\t\t\t{name} = new Dictionary<{TKey}, {TValue}>();\r\n";
readingStr += $"\t\t\tshort {name}Count = ReadShort(bytes, ref index);\r\n";
readingStr += $"\t\t\tfor (int i = 0; i < {name}Count; ++i)\r\n";
readingStr += $"\t\t\t\t{name}.Add({GetFieldReadingStr(TKey)}, {GetFieldReadingStr(TValue)});\r\n";
}
else if (type == "enum")
{
string data = field.Attributes["data"].Value;
readingStr += $"\t\t\t{name} = ({data})ReadInt(bytes, ref index);\r\n";
}
else
{
readingStr += $"\t\t\t{name} = {GetFieldReadingStr(type)};\r\n";
}
}
return readingStr;
}
//获取各种基本类型的反序列化语句字符串
private string GetFieldReadingStr(string type)
{
switch (type)
{
case "byte":
return $"ReadByte(bytes, ref index)";
case "int":
return $"ReadInt(bytes, ref index)";
case "short":
return $"ReadShort(bytes, ref index)";
case "long":
return $"ReadLong(bytes, ref index)";
case "float":
return $"ReadFloat(bytes, ref index)";
case "double":
return $"ReadDouble(bytes, ref index)";
case "bool":
return $"ReadBool(bytes, ref index)";
case "string":
return $"ReadString(bytes, ref index)";
default:
return $"ReadData<{type}>(bytes, ref index)";
}
}
制作生成消息类功能
消息类本身与数据类十分类似,只是多了一个包含消息ID和消息体长度的头数据
因此我们可以方便的复用之前在制作数据类功能时声明的方法,只是在生成语句方面需要添加一些特有的内容
假设要生成下面的消息类
1 | namespace GamePlayer |
分析可以发现,相对于数据类,
消息类的获取字节长度上多了获取消息ID和消息体的字节长度,序列化方法上多了序列化ID和消息体长度的逻辑,多了一个可以获取消息ID的方法
因此可以复制之前生成数据类的语句,在此基础上稍作修改即可完成生成消息类的逻辑
1 | [ ] |
相对于数据类作修改的地方:
- 需要额外从
<message>
节点的属性内获取消息ID - 添加额外的
GetID
方法,该方法唯一可变的地方是之前获取的消息ID - 在
GetBytesNum
中,添加固定的累加消息头字节长度的语句 - 在
Writing
中,添加固定的序列化消息ID和消息体长度的语句 - 修改存储路径
1 | public void GenerateMessage(XmlNodeList nodes) |