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

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

在开发网络游戏中,协议是必不可少的东西,一款游戏可能有非常多的协议,但是协议的重复性非常高,而且前端后端都需要,人工完成显然不现实,可以通过共同的配置去生成我们的协议

一.协议配置文件

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

<messages>
    
    <enum name="E_PLAYER_TYPE" namespace="GamePlayer">
        <field name="MAIN">1field>
        <field name="OTHER"/>
    enum>
    <enum name="E_MONSTER_TYPE" namespace="GameMonster">
        <field name="NORMAL">2field>
        <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);
    }

你可能感兴趣的:(Unity,c#,unity,学习)