目录
协议(消息)生成主要做什么?
知识点二 制作功能前的准备工作
编辑编辑 制作消息生成功能
实现效果
总结
上一篇中配置的XML文件可见:
https://mpbeta.csdn.net/mp_blog/creation/editor/147647176
协议(消息)生成主要做什么?
//协议生成 主要是使用配置文件中读取出来的信息
//动态的生成对应语言的代码文件
//每次添加消息或者数据结构类时,我们不需要再手写代码了
//我们不仅可以生成C#脚本文件,还可以根据需求生成别的语言的文件
#endregion
知识点二 制作功能前的准备工作
//协议生成是不会在发布后使用的功能,主要是在开发时使用
//所以我们在Unity当中可以把它作为一个编辑器功能来做
//因此我们可以专门新建一个Editor文件夹 (专门放编辑器相关内容,不会发布)
//在其中放置配置文件、自动生成相关脚本文件
Unity编辑器功能代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Xml;
public class ProtocolTool
{
private static string PROTO_PATH = Application.dataPath + "/Editor/protocolTool/protocolInfo.xml";
private static GeneratreCSharp generatreCSharp = new GeneratreCSharp();
[MenuItem("ProtocolTool/生成C#脚本")]
private static void GenerateCSharp()
{
//1.读取xml相关信息
//XmlNodeList list = GetNodes("enum");
//2.根据这些信息,去拼接字符串,生成对应的脚本
generatreCSharp.GeneratreEnum(GetNodes("enum"));
//刷新编辑器页面 让我们可以看到生成的内容 不需要手动刷新了
//生成数据结构类
generatreCSharp.GeneratreData(GetNodes("data"));
//生成消息类
generatreCSharp.GeneratreMsg(GetNodes("message"));
AssetDatabase.Refresh();
}
[MenuItem("ProtocolTool/生成C++脚本")]
private static void GenerateCpp()
{
Debug.Log("生成C++脚本");
}
[MenuItem("ProtocolTool/生成Java脚本")]
private static void GenerateJava()
{
Debug.Log("生成Java脚本");
}
private static XmlNodeList GetNodes(string nodeName)
{
XmlDocument xml = new XmlDocument();
xml.Load(PROTO_PATH);
XmlNode root= xml.SelectSingleNode("messages");
return root.SelectNodes(nodeName);
}
}


制作消息生成功能
根据配置好的XML文件来制作消息生成工具,分别实现生成枚举类型,数据结构类型(其中包括GetBytesNum,Writing,Reading函数),消息类型脚本
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using UnityEngine;
public class GeneratreCSharp
{
//协议保存路径
private string SAVE_PATH = Application.dataPath + "/Scripts/Protocol/";
//生成枚举脚本的逻辑
public void GeneratreEnum(XmlNodeList nodes)
{
string namespaceStr = "";
string enumNameStr = "";
string fieldStr = "";
foreach (XmlNode enumNodes in nodes)
{
//获取命名空间配置信息
namespaceStr = enumNodes.Attributes["namespace"].Value;
//获取枚举名配置信息
enumNameStr = enumNodes.Attributes["name"].Value;
//获取所有字段节点 然后进行字符串拼接
XmlNodeList enumFields = enumNodes.SelectNodes("field");
//一个新的枚举 需要清空一次上一次拼接的字段字符串
fieldStr = "";
foreach (XmlNode enumField in enumFields)
{
fieldStr += "\t\t" + enumField.Attributes["name"].Value;
if (enumField.InnerText != "")
fieldStr +="="+ enumField.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("枚举生成结束");
}
//生成数据结构类的逻辑
public void GeneratreData(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"+
$"namespace {namespaceStr}\r\n" +
"{\r\n" +
$"\tpublic class {classNameStr} : BaseData\r\n" +
"\t{\r\n" +
$"{fieldStr}" +
"\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"+
"\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"+
"\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("数据结构类生成结束");
}
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 += "\tpublic " + data + " ";
}
else
{
fieldStr += "\t\tpublic " + type + " ";
}
fieldStr += fieldName + ";\r\n";
}
return fieldStr;
}
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;
bytesNumStr += "\t\t\tnum +=2;\r\n";//+2是为了节约字节数 用一个short去存储信息
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]") + ";\r\n";
}
else if (type == "array")
{
string data = field.Attributes["data"].Value;
bytesNumStr += "\t\t\tnum +=2;\r\n";//+2是为了节约字节数 用一个short去存储信息
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]") + ";\r\n";
}
else if (type == "dic")
{
string Tkey = field.Attributes["Tkey"].Value;
string Tvalude = field.Attributes["Tvalue"].Value;
bytesNumStr += "\t\t\tnum +=2;\r\n";//+2是为了节约字节数 用一个short去存储信息
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") + ";\r\n";
bytesNumStr += "\t\t\t\tnum +=" + GetValueBytesNum(Tvalude, name + "[key]") + ";\r\n";
bytesNumStr += "\t\t\t}\r\n";
}
else
bytesNumStr += "\t\t\tnum +=" + GetValueBytesNum(type, name)+";\r\n";
}
return bytesNumStr;
}
private string GetValueBytesNum(string type,string name)
{
switch (type)
{
case "int":
case "float":
case "enum":
return "4";
case "long":
return "8";
case "byte":
case "bool":
return "1";
case "short":
return "2";
case "string":
return "4+Encoding.UTF8.GetByteCount(" + name + ")";
default:
return name + ".GetBytesNum()";
}
}
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" + GetWritingValueStr(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" + GetWritingValueStr(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" + GetWritingValueStr(Tkey, "key") + "\r\n";
writingStr += "\t\t\t\t" + GetWritingValueStr(Tvalue, name + "[key]") + "\r\n";
writingStr += "\t\t\t}\r\n";
}
else
{
writingStr += "\t\t\t" + GetWritingValueStr(type, name) + "\r\n";
}
}
return writingStr;
}
private string GetWritingValueStr(string type,string name)
{
switch (type)
{
case "byte":
return "WriteByte(bytes ," + name + ", ref index);";
case "int":
return "WriteInt(bytes ," + name + ", ref index);";
case "bool":
return "WriteBool(bytes ," + 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 "string":
return "WriteString(bytes ," + name + ", ref index);";
case "enum":
return "WriteInt(bytes ,Convert.ToInt32(" + name + "), ref index);";
default:
return "WriteData(bytes ," + name + ", ref index);";
}
}
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(" + GetReadingValueStr(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]" +" = "+ GetReadingValueStr(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(" + GetReadingValueStr(Tkey) + ", " +
GetReadingValueStr(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 + " = " + GetReadingValueStr(type) + ";\r\n";
}
return readingStr;
}
private string GetReadingValueStr(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 "bool":
return "ReadBool(bytes, ref index)";
case "string":
return "ReadString(bytes, ref index)";
default:
return "ReadData<" + type + ">(bytes, ref index)";
}
}
public void GeneratreMsg(XmlNodeList nodes)
{
string idStr = "";
string namespaceStr = "";
string classNameStr = "";
string fieldStr = "";
string GetBytesNumStr = "";
string WritingStr = "";
string ReadingStr = "";
foreach (XmlNode dataNode in nodes)
{
//消息ID
idStr = dataNode.Attributes["id"].Value;
//命名空间
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" +
$"namespace {namespaceStr}\r\n" +
"{\r\n" +
$"\tpublic class {classNameStr} : BaseMsg\r\n" +
"\t{\r\n" +
$"{fieldStr}" +
"\t\tpublic override int GetBytesNum()\r\n" +
"\t\t{\r\n" +
"\t\t\tint num=8;\r\n" +//这个8代表的是 消息ID的4个字节和消息长度的4个字节
$"{GetBytesNumStr}" +
"\t\t\treturn num;\r\n" +
"\t\t}\r\n" +
"\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" +
"\t\t\tWriteInt(bytes, GetID(), ref index);\r\n" +
"\t\t\tWriteInt(bytes, bytes.Length - 8, ref index);\r\n" +
$"{WritingStr}" +
"\t\t\treturn bytes;\r\n" +
"\t\t}\r\n" +
"\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\tpublic override int GetID()\r\n" +
"\t\t{\r\n" +
"\t\t\treturn " + idStr + ";\r\n"+
"\t\t}\r\n" +
"\t}\r\n" +
"}";
//保存文件的路径
string path = SAVE_PATH + namespaceStr + "/Msg/";
//如果不存在这个文件夹 则创建
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
//字符串保存 存储为枚举脚本文件
File.WriteAllText(path + classNameStr + ".cs", dataStr);
}
Debug.Log("数据结构类生成结束");
}
}
实现效果
点击生成C#脚本,就会在Unity编辑器中生成一个Protocol文件夹,里边有我们想要的消息脚本
以GamePlayer命名空间为例展示生成后的代码
这是Data文件夹脚本
using System;
using System.Collections.Generic;
using System.Text;
namespace 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 override int GetBytesNum()
{
int num=0;
num +=4;
num +=4;
num +=1;
num +=8;
num +=2;
for(int i = 0;i<arrays.Length;i++)
num +=4;
num +=2;
for(int i = 0;i< list.Count;i++)
num +=4;
num +=2;
foreach(int key in dic.Keys)
{
num +=4;
num +=4+Encoding.UTF8.GetByteCount(dic[key]);
}
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);
}
return bytes;
}
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));
return index - beginIndex;
}
}
}
这是Enum文件夹中脚本
namespace GamePlayer
{
public enum E_PLAYER_TYPE
{
MAIN=1,
OTHER,
}
}
这是Msg文件夹中脚本
using System;
using System.Collections.Generic;
using System.Text;
namespace GamePlayer
{
public class PlayerMsg : BaseMsg
{
public int playerID;
public PlayerData data;
public override int GetBytesNum()
{
int num=8;
num +=4;
num +=data.GetBytesNum();
return num;
}
public override byte[] Writing()
{
int index =0;
byte [] bytes =new byte[GetBytesNum()];
WriteInt(bytes, GetID(), ref index);
WriteInt(bytes, bytes.Length - 8, ref index);
WriteInt(bytes ,playerID, ref index);
WriteData(bytes ,data, ref index);
return bytes;
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index =beginIndex ;
playerID = ReadInt(bytes, ref index);
data = ReadData<PlayerData>(bytes, ref index);
return index - beginIndex;
}
public override int GetID()
{
return 1001;
}
}
}
总结
// 根据配置生成脚本的文件的主要思路就是
// 按规则拼接字符串
// 只要有数据和规则,我们就可以动态的创建脚本文件