Unity中添加 菜单栏功能

为编辑器菜单栏添加新的选项入口

  • 可以通过Unity提供我们的MenuItem特性在菜单栏添加选项按钮

  • 特性名:MenuItem

  • 命名空间:UnityEditor

    1
    2
    3
    4
    5
    6
    7
    8
    [MenuItem("GameTool/Test/Tools/生成Excel信息")]
    //规则一:必须为静态方法
    //规则二:菜单栏至少有一个斜杠
    //规则三:可以用在任意类中,不继承Mono也行
    private static void Test()
    {

    }

刷新Project窗口

  • 类名:AssetDatabase

  • 方法:Refresh

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [MenuItem("GameTool/Test/Tools/生成Excel信息")]
    //规则一:必须为静态方法
    //规则二:菜单栏至少有一个斜杠
    //规则三:可以用在任意类中,不继承Mono也行
    private static void Test()
    {
    Directory.CreateDirctory(Application.dataPath + "测试文件夹");
    AssetDatabase.Refresh();
    }

Editor文件夹

  • Editor文件夹可以放在项目的任何文件夹下,所以有多个

  • 放在其中的内容,项目打包时不会被打包到项目中

  • 一般编辑器相关代码都可以放在该文件夹中

Excel表

Excel表的本质

  • 本质就是一堆有自己存储读取规则的数据
  • Excel的Dll包以及把解析Excel表的相关类和方法写好了。

读取Excel表

  • 打开Excel表

    • 主要知识点

      1. FileStre 读取文件流
      2. IExcelDataReader类,从流中读取Excel数据
      3. DataSet 数据集合类 将Excel数据转存进其中方便读取
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      [MenuItem("GameTool/打开Excel表")]
      private static void OpenExcel()
      {
      using (FileStream fs = File.Open(Application.dataPath + "/ArtRes/Excel/TestInfo.xlsx", FileMode.Open, FileAccess.Read))
      {
      //通过文件获取excel数据
      IExcelDataReader excelDataReader = ExcelReaderFactory.CreateOpenXmlReader(fs);
      //将表中数据转换为DataSet数据
      DataSet result = excelDataReader.AsDataSet();
      //得到Excel文件中的所有表信息
      for (int i = 0; i < result.Tables.Count; i++)
      {
      Debug.Log("表名" + result.Tables[i].TableName);
      Debug.Log("行数" + result.Tables[i].Rows.Count);
      Debug.Log("列数" + result.Tables[i].Columns.Count);

      }
      fs.Close();
      }
      }

获取Excel表中单元格的信息

  • 主要知识点

    • FileStre 读取文件流

    • IExcelDataReader类,从流中读取Excel数据

    • DataSet 数据集合类 将Excel数据转存进其中方便读取

    • DataTable 数据表类 表示表格中一张表

    • DataRow 数据行类 表示某行中的一行数据

      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
      [MenuItem("GameTool/读取Excel表")]
      private static void ReadExcel()
      {
      using (FileStream fs = File.Open(Application.dataPath + "/ArtRes/Excel/TestInfo.xlsx", FileMode.Open, FileAccess.Read))
      {
      //通过文件获取excel数据
      IExcelDataReader excelDataReader = ExcelReaderFactory.CreateOpenXmlReader(fs);
      //将表中数据转换为DataSet数据
      DataSet result = excelDataReader.AsDataSet();
      //得到Excel文件中的所有表信息
      for (int i = 0; i < result.Tables.Count; i++)
      {
      //得到一张表中的信息
      DataTable table = result.Tables[i];
      DataRow row;
      for (int j = 0; j < table.Rows.Count; j++)
      {
      //得到每一行的信息
      row = table.Rows[j];
      for (int z = 0; z < table.Columns.Count; z++)
      {
      //读每一列
      Debug.Log(row[z].ToString());
      }

      }

      }
      fs.Close();
      }

      }

Excel配置表工具包实战

配表规则

  • 需求:数据结构类,表的容器类,二进制数据
  • 第一行:字段名

  • 第二行:字段类型

  • 第三行:容器的Key(id为key)

  • 第四行:注释

  • 第五行及以后:数据内容。

    image-20250727115147699

读取Excel目录下所有Excel文件

1
2
3
4
//记在指定路径中的所有Excel文件  用于生成对应的3个文件
DirectoryInfo dInfo = Directory.CreateDirectory(ExCEL_PATH);
//得到指定路径中的文件信息 相当于就是得到所有的Excel表
FileInfo[] files = dInfo.GetFiles();
  • files遍历就是所有的Excel表

生成数据结构类

  • 遍历所有Excel表

  • 从表中读取数据到DataTableCollection 中

  • 用字符串拼接数据 写入文件中

    特性 CreateOpenXmlReader CreateBinaryReader
    文件格式 .xlsx .xls
    性能 更高 较低
    内存占用 更低 较高
    旧格式兼容性 不支持 支持
    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
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    public class ExcellTool
    {
    /// <summary>
    /// excel文件存放的路径
    /// </summary>
    public static string ExCEL_PATH = Application.dataPath + "/ArtRes/Excel/";


    /// <summary>
    /// 数据结构类脚本存储路径
    /// </summary>
    public static string DATA_CLASS_PATH = Application.dataPath + "/Scripts/ExcelData/DataClass/";
    [MenuItem("GameTool/生成Excel的数据")]
    private static void GenerateExcelInfo()
    {
    //记在指定路径中的所有Excel文件 用于生成对应的3个文件
    DirectoryInfo dInfo = Directory.CreateDirectory(ExCEL_PATH);
    //得到指定路径中的文件信息 相当于就是得到所有的Excel表
    FileInfo[] files = dInfo.GetFiles();

    //数据表容器
    DataTableCollection result;

    for (int i = 0; i < files.Length; i++)
    {
    //因为一些meta的文件也会被检测出来,所以要检测后缀名
    //不是excel就不要处理了
    if (files[i].Extension != ".xlsx" && files[i].Extension != ".xls")
    {
    continue;
    }
    // Debug.Log(files[i].Name);
    using (FileStream fs = files[i].Open(FileMode.Open, FileAccess.Read))
    {
    IExcelDataReader excelReader = null;
    if (files[i].Extension == ".xlsx") //新版
    {
    excelReader = ExcelReaderFactory.CreateOpenXmlReader(fs);
    }
    else if(files[i].Extension == ".xls")
    {
    excelReader = ExcelReaderFactory.CreateBinaryReader(fs);
    }
    result = excelReader.AsDataSet().Tables;
    fs.Close();
    }
    //遍历文件中表的信息
    for (int j = 0; j < result.Count; j++)
    {
    //生成数据结构类
    GenerateExcelDataClass(result[j]);
    }
    }

    }

    private static void GenerateExcelDataClass(DataTable table)
    {
    //字段名行
    DataRow rowName = GetVariableNameRow(table);
    //字段类型行
    DataRow rowType = GetVariableTypeRow(table);


    //判断路径是否存在 不存在就 创建
    if (!Directory.Exists(DATA_CLASS_PATH))
    Directory.CreateDirectory(DATA_CLASS_PATH);
    //如果我们要生成对应的数据结构类脚本 其实就是 拼字符串 然后存数据
    string str = "public class " + table.TableName + "\n{\n";

    //变量进行字符串拼接
    for (int i = 0; i < table.Columns.Count; i++)
    {
    str += " public" + rowType[i].ToString() + " " + rowName[i] + ";\n";
    }
    str += "}";
    //把拼接好的字符串存到指定文件中
    File.WriteAllText(DATA_CLASS_PATH + table.TableName + ".cs", str);
    }


    /// <summary>
    /// 获取变量名所在行
    /// </summary>
    /// <param name="table"></param>
    /// <returns></returns>
    private static DataRow GetVariableNameRow(DataTable table)
    {
    return table.Rows[0];
    }


    private static DataRow GetVariableTypeRow(DataTable table)
    {
    return table.Rows[1];
    }
    }

生成容器类

  • 因为数据结构类只能得到一行的数据,而容器类可以得到整张表的数据
  • 取出表里面的键的对应列行 和表名
  • 拼接字符串
  • 写入文件夹
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
   /// <summary>
/// 生成Excel表对应的容器类
/// </summary>
/// <param name="table"></param>
private static void GenerateExcelContainer(DataTable table)
{
//获得索引
int keyIndex = GetKeyIndex(table);
//获得字段类型行
DataRow rowType = GetVariableTypeRow(table);

if (!Directory.Exists(DATA_CONTAINER_PATH))
Directory.CreateDirectory(DATA_CONTAINER_PATH);

string str = "using System.Collections.Generic;\n";
str += "public class " + table.TableName + "Container" + "\n{\n";

str += " public Dictionary<" + rowType[keyIndex].ToString() + ", " + table.TableName + ">";
str += " dataDic = new Dictionary<" + rowType[keyIndex].ToString() + ", " + table.TableName + "> ();\n";


str += "}";
File.WriteAllText(DATA_CONTAINER_PATH + table.TableName + "Container" + ".cs", str);
}


/// <summary>
/// 获取key的索引
/// </summary>
/// <param name="table"></param>
/// <returns></returns>
private static int GetKeyIndex(DataTable table)
{
DataRow row = table.Rows[2];
for (int i = 0; i < table.Columns.Count; i++)
{
if (row[i].ToString() == "key")
{
return i;
}

}
return 0;
}

生成2进制数据

  • 2进制数据读取后放在StreamingAssets方便以后读取打包

    1. 先要存储我们需要写多少行的数据 方便我们读取
    2. 存储主键的变量名,方便之后add进字典
    3. 根据类型决定如何写入数据
    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
    /// <summary>
    /// 生成excel2进制数据
    /// </summary>
    /// <param name="table"></param>
    private static void GenerateExcelBinary(DataTable table)
    {
    //没有就创建
    if (!Directory.Exists(DATA_BINARY_PATH))
    {
    Directory.CreateDirectory(DATA_BINARY_PATH);
    }
    //创建一个2进制文件写入
    using (FileStream fs = new FileStream(DATA_BINARY_PATH + table.TableName + ".www", FileMode.OpenOrCreate, FileAccess.Write))
    {
    //存储具体excel对应的2进制信息
    //1.先要存储我们需要写多少行的数据 方便我们读取
    fs.Write(BitConverter.GetBytes(table.Rows.Count - 4), 0, 4);
    //2.存储主键的变量名,方便之后add进字典
    string keyName = GetVariableTypeRow(table)[GetKeyIndex(table)].ToString();
    byte[] bytes = Encoding.UTF8.GetBytes(keyName);
    //存储字符串字节数组长度
    fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);
    //存储字符串字节数组
    fs.Write(bytes, 0, bytes.Length);

    DataRow row;
    //根据类型决定如何写入数据
    DataRow rowType = GetVariableTypeRow(table);
    for (int i = BEGIN_INDEX; i < table.Rows.Count; i++)
    {
    row = table.Rows[i];
    for (int j = 0; j < table.Columns.Count; j++)
    {
    switch (rowType[j].ToString())
    {
    case "int":
    fs.Write(BitConverter.GetBytes(int.Parse(row[j].ToString())), 0, 4);
    break;
    case "string":

    bytes = Encoding.UTF8.GetBytes(row[j].ToString());
    //写入字节数组长度
    fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);
    fs.Write(bytes, 0, bytes.Length);
    break;
    case "float":
    fs.Write(BitConverter.GetBytes(float.Parse(row[j].ToString())), 0, 4);
    break;
    case "bool":
    fs.Write(BitConverter.GetBytes(bool.Parse(row[j].ToString())), 0, 1);
    break;
    }
    }
    }
    fs.Close();
    }
    }

读取生成的2进制数据

  • 打开二进制文件 读取到字节数组

  • 读取主键名,用于后续字典键值

  • 用反射创建容器对象:

    ​ 用于获取里面字典字段和字典add函数

    通过泛型参数 T 的类型信息,使用 Activator.CreateInstance 创建容器对象 contaninerObj

  • 用反射创建数据结构对象:

    ​ 确定泛型参数 K 的类型(如 TowerInfo),用于后续数据解析。

  • 逐行解析数据

     将数据填充在反射创建的数据结构对象
    
  • 填充容器字典

    ​ 用反射获取容器内的字典,并且调用字典的Add方法,将数据对象的主键值(keyValue)和对象本身添加到字典中。

  • 最后将容器对象 contaninerObjtypeof(T).Name 为键存入静态字典 tableDic,供后续通过 GetTable<T> 方法获取。

    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
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    /// <summary>
    /// 加载Excel表的2进制数据到内存中
    /// </summary>
    /// <typeparam name="T">容器类</typeparam>
    /// <typeparam name="K">数据结构体类类名</typeparam>
    public void LoadTable<T, K>()
    {
    //读取excel2进制表
    using (FileStream fs = File.Open(DATA_BINARY_PATH + typeof(K) + ".www", FileMode.Open, FileAccess.Read))
    {
    byte[] bytes = new byte[fs.Length];
    fs.Read(bytes, 0, bytes.Length);
    fs.Close();


    //记录读了多少字节
    int index = 0;
    //读取多少行
    int count = BitConverter.ToInt32(bytes, index);
    index += 4;
    //读取主键名字
    int keyNameLength = BitConverter.ToInt32(bytes, index);
    index += 4;
    string keyName = Encoding.UTF8.GetString(bytes, index, keyNameLength);
    index += keyNameLength;


    //创建容器类对象
    Type contaninerType = typeof(T);
    object contaninerObj = Activator.CreateInstance(contaninerType);
    //得到数据结构类的Type
    Type classType = typeof(K);
    //通过反射得到数据结构类 所有字段的信息
    FieldInfo[] infos = classType.GetFields();
    //读取每行数据
    for (int i = 0; i < count; i++)
    {
    //实例化一个数据结构类对象
    object dataObj = Activator.CreateInstance(classType);
    foreach (FieldInfo info in infos)
    {
    if (info.FieldType == typeof(int))
    {
    info.SetValue(dataObj, BitConverter.ToInt32(bytes, index));
    index += 4;
    }
    else if ((info.FieldType == typeof(float)))
    {
    info.SetValue(dataObj, BitConverter.ToSingle(bytes, index));
    index += 4;
    }
    else if ((info.FieldType == typeof(bool)))
    {
    info.SetValue(dataObj, BitConverter.ToBoolean(bytes, index));
    index += 1;
    }
    else if ((info.FieldType == typeof(string)))
    {
    int strLength = BitConverter.ToInt32(bytes, index);
    index += 4;
    info.SetValue(dataObj, Encoding.UTF8.GetString(bytes, index, strLength));
    index += strLength;

    }
    }
    //读取完一行数据应该加到字典中
    //得到容器里面的字典字段
    //通过反射可以通过获得GetFields()具体的信息,通过Get/SetValue可以获取设置字段里面的引用或者值
    object dicObject = contaninerType.GetField("dataDic").GetValue(contaninerObj);

    //通过字典对象得到其中的Add方法
    MethodInfo mInfo = dicObject.GetType().GetMethod("Add");

    //得到数据结构类对象中,指定主字段的值 /键
    object keyValue = classType.GetField(keyName).GetValue(dataObj);
    mInfo.Invoke(dicObject, new object[] { keyValue, dataObj });

    }
    //把读取完的表记录下来
    tableDic.Add(typeof(T).Name, contaninerObj);
    fs.Close();

    }
    }


    public T GetTable<T>() where T : class
    {
    string tableName = typeof(T).Name;
    if (tableDic.ContainsKey(tableName))
    {
    return tableDic[tableName] as T;
    }
    return null;
    }