UN5L2——协议(消息)配置

选择哪种格式配置协议?

利用配置文件配置消息、数据结构、枚举的目的

  1. 减少工作量,配置一次,之后自动化生成各种语言对应的类文件
  2. 减少沟通成本,避免前后端语言不同时,手动写代码出现前后端不统一的问题

我们可以选择:xml、json、excel、自定义等等

我们可以根据自己的喜好选择选择方便配置的,好用的即可
配置的主要目的是确定类名、成员变量名

之后根据读取的这些配置信息,再通过代码按照规则自动生成对应的类文件

本课使用xml作为协议配置文件,学会xml配置,其它的方式都是大同小异的
我们主要是学习制作思路和流程,以后的项目中,大家根据自己的喜好选择即可

XML相关知识,可以在数据持久化之XML中进行学习

以xml配置为例 —— 制定配置规则

我们主要执行以下步骤:

  1. 创建xml配置文件

  2. 制定配置规则

    首先,XML配置规则需要一个根节点,因此我们将<messages>​作为根节点,其中包裹各个枚举、消息类和枚举的声明

    1
    2
    3
    <?xml version="1.0" encoding="UTF-8"?>
    <messages>
    </messages>
    1. 枚举规则

      假设我们要创建这样的枚举

      1
      2
      3
      4
      5
      6
      7
      8
      namespace GamePlayer
      {
      public enum E_PLAYER_TYPE
      {
      MAIN = 1,
      OTHER,
      }
      }

      分析得到,要创建这样的枚举,必须要的消息是:枚举所在的命名空间,枚举名,各个字段名,各字段的默认值

      因此,我们规定这样的XML配置规则:

      • <enum>​节点代表一个枚举的声明,属性包括枚举名和命名空间名,其中包裹若干个字段
      • <field>​节点代表一个字段,属性有名字,<field>​可以包裹值,包裹的值即该字段默认值,也可以不包裹值

      按照如上规则,可以得到这样的XML配置文本

      1
      2
      3
      4
      5
      6
      7
      8
      <?xml version="1.0" encoding="UTF-8"?>
      <messages>
      <!-- 枚举配置规则 -->
      <enum name="E_PLAYER_TYPE" namespace="GamePlayer">
      <field name="MAIN">2</field>
      <field name="OTHER"/>
      </enum>
      </messages>

      按照这个规则,我们就再用XML声明一个E_MONSTER_TYPE​,声明得到:

      1
      2
      3
      4
      5
      6
      7
      8
      <?xml version="1.0" encoding="UTF-8"?>
      <messages>
      <!-- 枚举配置规则 -->
      <enum name="E_MONSTER_TYPE" namespace="GameMonster">
      <field name="NORMAL">2</field>
      <field name="BOSS"/>
      </enum>
      </messages>
    2. 数据类规则

      假设我们要创建这样的数据类:

      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
      namespace GamePlayer
      {
      public class PlayerData : BaseData
      {
      public int id;
      public float atk;
      public long lev;
      public int[] arrays;
      public List<int> list;
      public Dictionary<int, string> dict;

      public override int GetBytesNum()
      {
      throw new System.NotImplementedException();
      }

      public override int Reading(byte[] bytes, int beginIndex = 0)
      {
      throw new System.NotImplementedException();
      }

      public override byte[] Writeing()
      {
      throw new System.NotImplementedException();
      }
      }
      }

      值得一提的是,在配置文件里只需要各个字段(成员变量),而不需要方法,
      因为数据类的方法有哪些是固定的,而这些固定方法的具体实现依托于该数据类的各个字段
      因此配置文件内只需要各个字段的信息,就可以根据这些字段信息实现数据类的中的固定方法

      最终分析得到,要创建这样的数据类,必须要的消息是:所在的命名空间,类名,各个字段名,类型,若有泛型则泛型参数是什么

      因此,我们规定这样的XML配置规则:

      • <data>​节点代表一个数据类的声明,属性包括类名和命名空间名,其中包裹若干个字段
      • <field>​节点代表一个字段,属性有名字,变量类型,泛型参数(有泛型的情况下),
        <field>​可以包裹值,包裹的值即该字段默认值,也可以不包裹值

      按照如上规则,可以得到这样的XML配置文本

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <?xml version="1.0" encoding="UTF-8"?>
      <messages>
      <!-- 数据结构类配置规则 -->
      <data name="PlayerData" namespace="GamePlayer">
      <field type="int" name="id"/>
      <field type="float" name="atk"/>
      <field type="bool" name="sex"/>
      <field type="long" name="lev"/>
      <field type="array" data="int" name="arrays"/>
      <field type="list" T="int" name="list"/>
      <field type="dic" TKey="int" TValue="string" name="dic"/>
      </data>
      </messages>
    3. 消息类规则

      假设我们要创建这样的消息类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      namespace GamePlayer
      {
      public class PlayerMessage : BaseMessage
      {
      public int PlayerId;
      public PlayerData PlayerData;

      public override int GetID()
      {
      return 1001;
      }
      }
      }

      所有的消息类都需要有一个自己的消息ID

      最终分析得到,要创建这样的数据类,必须要的消息是:所在的命名空间,类名,消息ID,各个字段名,类型等

      因此,我们规定这样的XML配置规则:

      • <message>​节点代表一个数据类的声明,属性包括消息ID,类名和命名空间名,其中包裹若干个字段
      • <field>​节点代表一个字段,属性有名字,变量类型,泛型参数(有泛型的情况下),
        <field>​可以包裹值,包裹的值即该字段默认值,也可以不包裹值

      按照如上规则,可以得到这样的XML配置文本

      1
      2
      3
      4
      5
      6
      7
      8
      <?xml version="1.0" encoding="UTF-8"?>
      <messages>
      <!-- 消息类配置规则 -->
      <message id="1001" name="PlayerMsg" namespace="GamePlayer">
      <field type="int" name="playerID"/>`
      <field type="PlayerData" name="data"/>
      </message>
      </messages>

      根据以上规则,我们再声明一个心跳消息ID,因为心跳消息不包含其他数据,因此不需要包裹内容

      1
      2
      3
      4
      <?xml version="1.0" encoding="UTF-8"?>
      <messages>
      <message id="1002" name="HeartMsg" namespace="GameSystem"/>
      </messages>

读取配置信息

对于XML消息的读取与解析,我们需要调用XmlDocument​

读取配置消息的代码如下:

  • 首先读取配置文件和根节点

    1
    2
    3
    XmlDocument xml = new XmlDocument();
    xml.Load(Application.dataPath + "/Scripts/L35_协议(消息)配置/Lesson35.xml");
    XmlNode root = xml.SelectSingleNode("messages");
  • 读取枚举消息

    先读取所有的<enum>​节点,然后遍历,获取其枚举名和命名空间名,
    然后读取每个<enum>​节点的所有<field>​节点,然后遍历,读取其名字和值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    XmlNodeList enumList = root.SelectNodes("enum");
    print("**********枚举**********");
    foreach (XmlNode enumNode in enumList)
    {
    print("枚举名:" + enumNode.Attributes["name"].Value);
    print("枚举命名空间名:" + enumNode.Attributes["namespace"].Value);
    XmlNodeList fields = enumNode.SelectNodes("field");
    foreach (XmlNode fieldNode in fields)
    {
    string str = fieldNode.Attributes["name"].Value;
    if (!string.IsNullOrEmpty(fieldNode.InnerText))
    str += " = " + fieldNode.InnerText;
    str += ", ";
    print(str);
    }
    }
  • 读取数据类

    先读取所有的<data>​节点,然后遍历,获取其数据类名和命名空间名,
    然后读取每个<data>​节点的所有<field>​节点,然后遍历,读取其变量类型,名字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    print("**********数据类**********");
    XmlNodeList dataList = root.SelectNodes("data");
    foreach (XmlNode dataNode in dataList)
    {
    print("数据类名:" + dataNode.Attributes["name"].Value);
    print("数据类命名空间名:" + dataNode.Attributes["namespace"].Value);
    XmlNodeList fields = dataNode.SelectNodes("field");
    foreach (XmlNode fieldNode in fields)
    {
    print(fieldNode.Attributes["type"].Value + " " + fieldNode.Attributes["name"].Value + ";");
    }
    }
  • 读取消息类

    先读取所有的<message>​节点,然后遍历,获取其消息类名和命名空间名,以及消息ID
    然后读取每个<message>​节点的所有<field>​节点,然后遍历,读取其变量类型,名字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    print("**********消息类**********");
    XmlNodeList msgList = root.SelectNodes("message");
    foreach (XmlNode msgNode in msgList)
    {
    print("消息类名:" + msgNode.Attributes["name"].Value);
    print("消息类命名空间名:" + msgNode.Attributes["namespace"].Value);
    print("消息ID:" + msgNode.Attributes["id"].Value);
    XmlNodeList fields = msgNode.SelectNodes("field");
    foreach (XmlNode fieldNode in fields)
    {
    print(fieldNode.Attributes["type"].Value + " " + fieldNode.Attributes["name"].Value + ";");
    }
    }