解析流程:
1. 配置Excel
2. 读取解析Excel (引用Excel.dll, ICSharpCode.SharpZipLib.dll)
3. 重新整理解析到的数据
4. 创建Excel对应的C#类,声明变量
5. 在类的构建函数中给变量赋值
6. 保存类文件,可在游戏直接使用
优点:
1. 每个Excel对应一个类,使用灵活,对Excel限制少
2. 自动创建脚本,不需要对每个Excel手动写代码
3. 数据值直接写到脚本里,方便查看,可以手动修改调整,不需要每次改动都在Excel里操作
4. 在游戏内直接调用类,不需要额外操作
缺点:
1. 表格格式确定后不能轻易修改,改动可能影响类的结构,代码报错
2. 因为是把数据直接写入到脚本内,数据量较大时可能会有问题
具体流程:
1. 配置Excel
格式:
预留前两行,可以写一些备注;第三行为数据格式;第四行为变量名字,第五行以后是数据内容
第一列为id,后面每一列为一个变量
Sheet页的名字,是创建类的类名
2. 解析Excel
3. 整理数据
解析Excel时,是按照行顺序,一行一行解析的,将每行数据记录下来,方式有很多,能满足使用要求即可
//注释说明
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using Excel;
using System.Reflection;
using System;
using System.Text;
using System.Linq;
public class ExcelReader
{
const int excelTypeRow = 3;
const int excelNameRow = 4;
static string excelFilePath = Application.dataPath + "/Excel";
static string excelCodePath = Application.dataPath + "/Script/Excel/AutoCreate";
public static void Read()
{
//读取所有Excel文件
string[] excelFiles = Directory.GetFiles(excelFilePath, "*.xlsx");
if (excelFiles.Length == 0)
{
Debug.Log("Excel file count == 0");
return;
}
//代码类
List<KeyValuePair<string, string>> codeList = new List<KeyValuePair<string, string>>();
//遍历所有Excel
for (int i = 0; i < excelFiles.Length; i++)
{
//打开Excel
string excelFile = excelFiles[i];
FileStream stream = File.Open(excelFile, FileMode.Open, FileAccess.Read);
//解析Excel
IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
if (!excelReader.IsValid)
{
Debug.Log("Invalid excel : " + excelFile);
continue;
}
string[] propertyTypes = null;
string[] propertyNames = null;
List<List<KeyValuePair<string, string>>> allItemRowList = new List<List<KeyValuePair<string, string>>>();
int rowIndex = 1;
//开始读取
while (excelReader.Read())
{
//读取每一行的数据
string[] datas = new string[excelReader.FieldCount];
for (int j = 0; j < excelReader.FieldCount; ++j)
{
datas[j] = excelReader.GetString(j);
}
//空行不处理
if (datas.Length == 0 || string.IsNullOrEmpty(datas[0]))
{
rowIndex++;
continue;
}
if (rowIndex == excelTypeRow)
{
//行数据表示类型
propertyTypes = datas;
}
else if (rowIndex == excelNameRow)
{
//行数据表示变量名
propertyNames = datas;
}
else if (rowIndex > excelNameRow)
{
//行数据表示变量值
List<KeyValuePair<string, string>> itemList = new List<KeyValuePair<string, string>>();
//遍历一行里的每个数据
//for (int j = 0; j < datas.Length; ++j)
//{
// if (j >= names.Length)
// continue;
// //读取数据
// itemList.Add(new KeyValuePair<string, string>(names[j], datas[j]));
//}
for (int j = 0; j < propertyNames.Length; j++)
{
if (j < datas.Length)
itemList.Add(new KeyValuePair<string, string>(propertyNames[j], datas[j]));
else
itemList.Add(new KeyValuePair<string, string>(propertyNames[j], null));
}
allItemRowList.Add(itemList);
}
rowIndex++;
}
if (propertyNames == null || propertyNames.Length == 0
|| propertyTypes == null || propertyTypes.Length == 0
|| propertyNames.Length != propertyTypes.Length)
continue;
//变量对应的所有值
Dictionary<string, List<string>> propertyValueDic = new Dictionary<string, List<string>>();
for (int k = 0; k < propertyNames.Length; k++)
{
propertyValueDic.Add(propertyNames[k], new List<string>());
}
for (int m = 0; m < allItemRowList.Count; m++)
{
for (int n = 0; n < allItemRowList[m].Count; n++)
{
propertyValueDic[allItemRowList[m][n].Key].Add(allItemRowList[m][n].Value);
}
}
//类名
string className = excelReader.Name + "Table";
//根据数据生成C#脚本
ScriptGenerator generator = new ScriptGenerator(className, propertyNames, propertyTypes, propertyValueDic);
string codeStr = generator.CreateCode();
//生成的类
codeList.Add(new KeyValuePair<string, string>(className, codeStr));
}
if (!Directory.Exists(excelCodePath))
Directory.CreateDirectory(excelCodePath);
for (int i = 0; i < codeList.Count; i++)
{
string className = codeList[i].Key;
string codeStr = codeList[i].Value;
StreamWriter sw = new StreamWriter(excelCodePath + "/" + className + ".cs");
sw.WriteLine(codeStr);
sw.Close();
}
UnityEditor.AssetDatabase.Refresh();
Debug.Log("<color=green>Auto Scripts Success : </color>" + codeList.Count);
}
}
4. 创建C#类
5. 变量赋值
创建脚本,这里采用拼接字符串的方式,变量赋值也是拼接的字符串,然后将拼接好的文本保存为".cs"文件
在参考链接中是生成dll文件,这种方式感觉高级一些
方法有很多,不局限于这两种,能实现功能就好
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Text;
using System;
public class ScriptGenerator
{
//脚本生成器
string className;
string[] propertyNames;
string[] propertyTypes;
Dictionary<string, List<string>> propertyValueDic;
public ScriptGenerator(string tableName, string[] names, string[] types, Dictionary<string, List<string>> valueDic)
{
className = tableName;
propertyNames = names;
propertyTypes = types;
propertyValueDic = valueDic;
}
//创建代码
public string CreateCode()
{
//生成类
StringBuilder classSource = new StringBuilder();
classSource.Append("/*Auto create, Don't Edit it*/\n");
classSource.Append("\n");
classSource.Append("using System;\n");
classSource.Append("using System.Reflection;\n");
classSource.Append("using System.Collections.Generic;\n");
classSource.Append("\n");
classSource.Append("[Serializable]\n");
classSource.Append("public class " + className + " : TableBase\n");
classSource.Append("{\n");
//声明变量
for (int i = 0; i < propertyNames.Length; i++)
{
classSource.Append(PropertyString(propertyNames[i], propertyTypes[i]));
}
//构造函数内赋值
classSource.Append("\n");
classSource.Append("\tpublic " + className + "()\n");
classSource.Append("\t{\n");
for (int i = 0; i < propertyNames.Length; i++)
{
classSource.Append(Assignment(propertyNames[i], propertyTypes[i], propertyValueDic[propertyNames[i]]));
}
classSource.Append("\t}\n");
//Get方法
classSource.Append("\n");
classSource.Append("\tint targetIndex;\n");
classSource.Append("\n");
for (int i = 0; i < propertyNames.Length; i++)
{
if (propertyNames[i] == "id")
continue;
classSource.Append("\tpublic " + propertyTypes[i] + " Get" + propertyNames[i].ToUpper() + "(string targetId)\n");
classSource.Append("\t{\n");
classSource.Append("\t\ttargetIndex = id.IndexOf(targetId);\n");
classSource.Append("\t\tif (targetIndex < 0)\n");
classSource.Append("\t\t\treturn default;\n");
classSource.Append("\t\treturn " + propertyNames[i] + "[targetIndex];\n");
classSource.Append("\t}\n");
}
//
classSource.Append("}\n");
return classSource.ToString();
}
private string PropertyString(string name, string type)
{
if (string.IsNullOrEmpty(name))
return null;
if (type == "int")
type = "List<int>";
else if (type == "float")
type = "List<float>";
else
type = "List<string>";
string propertyStr = "\tpublic " + type + " " + name + ";\n";
return propertyStr;
}
private string Assignment(string name, string type, List<string> valueList)
{
StringBuilder source = new StringBuilder();
source.Append("\t\t" + name + " = new List<" + type + ">()\n");
source.Append("\t\t{\n");
source.Append("\t\t\t");
Func<string, string> CheckPropertyAct;
if (type == "int")
CheckPropertyAct = CheckPropertyInt;
else if (type == "float")
CheckPropertyAct = CheckPropertyFloat;
else
CheckPropertyAct = CheckPropertyString;
for (int i = 0; i < valueList.Count; i++)
{
source.Append(CheckPropertyAct(valueList[i]) + ", ");
}
source.Append("\n");
source.Append("\t\t};\n");
return source.ToString();
}
private string CheckPropertyInt(string value)
{
return Convert.ToInt32(value).ToString();
}
private string CheckPropertyFloat(string value)
{
return Convert.ToSingle(value) + "f";
}
private string CheckPropertyString(string value)
{
return "\"" + value + "\"";
}
}
6. 把脚本文本保存为".cs"文件,在游戏里就可以直接使用咯~
-------------------- 分割线 --------------------
理论改良版:
1. 还是将Excel转为对应的C#类,不过只创建数据List(或者Dictionary),不赋值,即不保存数据,只记录数据类型
2. 将Excel数据用txt、json或byte二进制保存到本地(json的话可以对应类的格式)
3. 读取数据时初始化对应的类,获取Excel数据进行赋值
-------------------- 分割线 --------------------
改良进阶版: