Excel配置表小工具
Unity中添加 菜单栏功能
为编辑器菜单栏添加新的选项入口
可以通过Unity提供我们的
MenuItem
特性在菜单栏添加选项按钮特性名:MenuItem
命名空间:UnityEditor
1
2
3
4
5
6
7
8 [ ]
//规则一:必须为静态方法
//规则二:菜单栏至少有一个斜杠
//规则三:可以用在任意类中,不继承Mono也行
private static void Test()
{
}
刷新Project窗口
类名:AssetDatabase
方法:Refresh
1
2
3
4
5
6
7
8
9 [ ]
//规则一:必须为静态方法
//规则二:菜单栏至少有一个斜杠
//规则三:可以用在任意类中,不继承Mono也行
private static void Test()
{
Directory.CreateDirctory(Application.dataPath + "测试文件夹");
AssetDatabase.Refresh();
}
Editor文件夹
Editor文件夹可以放在项目的任何文件夹下,所以有多个
放在其中的内容,项目打包时不会被打包到项目中
一般编辑器相关代码都可以放在该文件夹中
Excel表
Excel表的本质
- 本质就是一堆
有自己存储读取规则的数据
- Excel的Dll包以及把解析Excel表的相关类和方法写好了。
读取Excel表
打开Excel表
主要知识点
- FileStre 读取文件流
- IExcelDataReader类,从流中读取Excel数据
- DataSet 数据集合类 将Excel数据转存进其中方便读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 [ ]
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 [ ]
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)
第四行:注释
第五行及以后:数据内容。
读取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/";
[ ]
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
方便以后读取打包
- 先要存储我们需要写多少行的数据 方便我们读取
- 存储主键的变量名,方便之后add进字典
- 根据类型决定如何写入数据
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
)和对象本身添加到字典中。最后将容器对象
contaninerObj
以typeof(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;
}