以下代码都放入一个ExportExcel脚本下
下面的文件全部放在一个文件中,且在Editor文件夹下
private static string ExcelPath = Application.dataPath.Replace("Assets", "Excel");
private static string IgnoreChar = "#";
private static string exportDirPath = Application.dataPath + "/Scripts/Data/";
private static string templateFile = Application.dataPath + "/Editor/Excel/Template.txt";
[MenuItem("Tools/导出/快速导出配置表")]
public static void Export()
{
EditorUtility.DisplayProgressBar("根据Excel导出cs文件", "导出中...", 0f);//创建加载条
try
{
//返回ExcelPath路径下所以的子文件的路径
string[] tableFullPaths = Directory.GetFiles(ExcelPath, "*", SearchOption.TopDirectoryOnly);
foreach (string path in tableFullPaths)
{
Debug.Log($"导出中...{path}");
StartExport(path);//遍历所有的excel文件
}
AssetDatabase.Refresh();//unity重新从硬盘读取更改的文件
Debug.Log("导出完成");
}
catch
{
Debug.LogError("导出失败");
}
finally
{
EditorUtility.ClearProgressBar();删除加载条
}
}
支持快速导出和全部导出2个模式,在编辑器菜单栏,“Tools/导出/导出全部配置表”
public static bool isFastExport = true;
[MenuItem("Tools/导出/导出全部配置表")]
private static void FastExport()
{
isFastExport = false;
Export();
}
一般将Excel文件放在和Assets文件夹同级路径下,防止unity重新加载资源
ExportFile方法,利用NPOI的api读取xlsx文件的每个单元格,存储到sheetDict字典
怎么只导出修改的xlsx文件?
public static Dictionary<string, string> hashDict = new Dictionary<string, string>();
public static StringBuilder allHash = new StringBuilder();
public static void StartExport(string[] tableFullPaths)
{
string hashFile = ExcelPath + "/AllFileHash.txt";//hash值文件,和xlsx文件在同一个目录下
bool isExitHashFile = File.Exists(hashFile);//是否是第一次导出
if (!isExitHashFile || !isFastExport)//不存在hash文件或不是快速导出,导出全部的xlsx文件
{
foreach (string tableFileFullPath in tableFullPaths)
{
if (tableFileFullPath.EndsWith(".xls") || tableFileFullPath.EndsWith(".xlsx"))
{
Debug.LogFormat("开始导出配置: {0}", Path.GetFileName(tableFileFullPath));
string hash = CalculateFileHash(tableFileFullPath);//计算文件的hash值
int startIndex = tableFileFullPath.IndexOf("\\");//E:/unitycode/项目名称/Excel\Test.xlsx
string tempFile = tableFileFullPath.Substring(startIndex + 1);//Test.xlsx
allHash.Append($"{tempFile}:{hash}\n");//Test.xlsx:5F6342BC7B0387...
StartPieceExport(tableFileFullPath);//导出所有的xlsx文件
}
}
}
else
{
ReadFileToDict(hashFile);//读取hash文件进一个字典,文件名->hash值
foreach (string tableFileFullPath in tableFullPaths)
{
if (tableFileFullPath.EndsWith(".xls") || tableFileFullPath.EndsWith(".xlsx"))
{
string hash = CalculateFileHash(tableFileFullPath);//计算哈市值
int startIndex = tableFileFullPath.IndexOf("\\");//E:/unitycode/项目名称/Excel\Test.xlsx
string tempFile = tableFileFullPath.Substring(startIndex + 1);//Test.xlsx
allHash.Append($"{tempFile}:{hash}\n");
if (hashDict.ContainsKey(tempFile))//字典存在文件名
{
if (hashDict[tempFile] != hash)//字典存在文件名,hash值不相等
{
StartPieceExport(tableFileFullPath);//文件更改了
Debug.Log("导出了" + tableFileFullPath);
}
else
{
//存在,文件没有改变
}
}
else//字典不存在文件名,文件为新增文件
{
StartPieceExport(tableFileFullPath);
//文件增加了
}//xlsx文件被删除的情况没有处理,需要手动删除对应的cs文件
}
}
}
SavehashFile(hashFile);//将allHash写入文件
}
下面为计算存储hash相关的方法
hash相关
public static void ReadFileToDict(string hashFile)
{
string output = File.ReadAllText(hashFile);//读取指定路径文件到一个字符串
hashDict = ParseStringToDictionary(output);//将字符串解析为一个字典
}
public static Dictionary<string, string> ParseStringToDictionary(string input)
{
//对话系统.xlsx:5F6342BC7B03878CFB...语言系统.xlsx:530D69327A0FA9A7A6...
Dictionary<string, string> dictionary = new Dictionary<string, string>();
//分割字符串,每一行是一个字符串
string[] lines = input.Split(new[] { "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
//对话系统.xlsx:5F6342BC7B03878CFB...
//语言系统.xlsx:530D69327A0FA9A7A6...
foreach (string line in lines)
{//对话系统.xlsx:5F6342BC7B03878CFB...
string[] parts = line.Split(':');//使用:分割字符串
if (parts.Length == 2)
{
string key = parts[0].Trim();//对话系统.xlsx
string value = parts[1].Trim();//5F6342BC7B03878CFB...
dictionary[key] = value;
}
}
return dictionary;
}
public static string CalculateFileHash(string filePath)//计算一个文件的的hash值
{
var hash = SHA256.Create();
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);//打开文件流
byte[] hashByte = hash.ComputeHash(stream);//计算文件流的hash值
stream.Close();//关闭文件流
return BitConverter.ToString(hashByte).Replace("-", "");//将字节数组转化为字符串,替换-
}
public static void SavehashFile(string hashFile)//将一个字符串存储在文件中
{
using (StreamWriter sw = new StreamWriter(File.Create(hashFile)))
sw.Write(allHash);
Debug.Log("hashFile:" + hashFile);
}
hash相关
private static void StartPieceExport(string path)
{
//每一个sheet表->二维单元格
Dictionary<string, List<List<string>>> sheetDict = new Dictionary<string, List<List<string>>>();
FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);//创建文件流读写每一个xlsx文件
IWorkbook book = null;
if (path.EndsWith(".xls"))//如果是xls文件,传入二进制文件流
book = new HSSFWorkbook(stream);
else if (path.EndsWith(".xlsx"))
book = new XSSFWorkbook(path);//传入文件路径
else
return;
int sheetNum = book.NumberOfSheets;//获取一个excel文件表的数量
stream.Close();//关闭文件流
//处理每一个sheet
for (int sheetIndex = 0; sheetIndex < sheetNum; sheetIndex++)
{
string sheetName = book.GetSheetName(sheetIndex);//获取每个sheet文件的名字
//名字以#为前缀,要忽略的sheet,以sheet为前缀,excel默认的名字,忽略
if (sheetName.StartsWith(IgnoreChar) || sheetName.StartsWith("Sheet")) { continue; }
ISheet sheet = book.GetSheetAt(sheetIndex);//得到每个sheet对象
sheet.ForceFormulaRecalculation = true; //强制公式计算,执行excel自身支持的函数,如AVERAGE(D2:K5)
int MaxColumn = sheet.GetRow(0).LastCellNum;//得到第一行的列数
List<List<string>> FilterCellInfoList = new List<List<string>>();
//处理每一行
for (int rowId = 0; rowId <= sheet.LastRowNum; rowId++)
{
IRow sheetRowInfo = sheet.GetRow(rowId);
if (sheetRowInfo == null) Debug.LogError("无法获取sheetRowInfo数据");
var firstCell = sheetRowInfo.GetCell(0);//得到第一列
if (firstCell == null||firstCell.ToString().Contains(IgnoreChar)) { continue; }//该行的第一列无效,跳过该行
if (firstCell.CellType == CellType.Blank || firstCell.CellType == CellType.Unknown || firstCell.CellType == CellType.Error) { continue; }
List<string> rowList = new List<string>();//存储每一行的单元格
//处理每一个单元格
for (int columIndex = 0; columIndex < MaxColumn; columIndex++)
{
ICell cell = sheetRowInfo.GetCell(columIndex);//得到第rowId行的第columIndex个单元格
if (cell != null && cell.IsMergedCell)
{//单元格不为空并且为可以合并的单元格
cell = GetMergeCell(sheet, cell.RowIndex, cell.ColumnIndex);
}
else if (cell == null)
{// 有时候合并的格子索引为空,就直接通过索引去找合并的格子
cell = GetMergeCell(sheet, rowId, columIndex);
}
//计算结果,支持逻辑表达式
if (cell != null && cell.CellType == CellType.Formula)
{
cell.SetCellType(CellType.String);
rowList.Add(cell.StringCellValue.ToString());//将单元格加入这一行
}
else if (cell != null)
{
rowList.Add(cell.ToString());
}
else
{
rowList.Add("");
}
}
FilterCellInfoList.Add(rowList);//将行数据加入表中
}
sheetDict.Add(sheetName, FilterCellInfoList);//将sheet名字和对应的表数据,加入字典
}
foreach (var item in sheetDict)
{
string fileName = item.Key;
string dirPath = exportDirPath;
ParseExcelToCS(item.Value, item.Key, fileName, dirPath);//将过滤记录好的表数据->cs文件
}//item.Value=>sheetName(类名),item.Key=>表格描述
}
GetMergeCell,合并单元格
private static ICell GetMergeCell(ISheet sheet, int rowIndex, int colIndex)
{
//获取合并单元个的总数
for (int i = 0; i < sheet.NumMergedRegions; i++)
{
//获取第一个合并单元格
var cellrange = sheet.GetMergedRegion(i);
//如果在单元格范围
if (colIndex >= cellrange.FirstColumn && colIndex <= cellrange.LastColumn
&& rowIndex >= cellrange.FirstRow && rowIndex <= cellrange.LastRow)
{
//返回第一个单元格
var row = sheet.GetRow(cellrange.FirstRow);
var mergeCell = row.GetCell(cellrange.FirstColumn);
return mergeCell;
}
}
return null;
}
ParseExcelToCS,将数据导出为cs文件,写入文件,使用模板替换的方式,预先写好一个.txt的文件
下面是Template.txt文件,Assets/Editor/Excel/Template.txt
将数据写入字典,
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class _DataClassName
{
_DataClassFields
public _DataClassName(_DataClassParameter)
{
_DataClassFun
}
}
public static class _Data_XX
{
public static Dictionary data = new Dictionary()
{
_DictContent
};
}
ParseExcelToCS
写xlsx文件的规则 上面连续 1:备注,2:字段名,3:字段类型,第1,2,3行可以是备注,后面接字段
根据自己的习惯修改
static string _DataClassName = "_DataClassName";
static string _DataClassParameter = "_DataClassParameter";
static string _DataClassFun = "_DataClassFun";
static string _Data_XXDict = "_Data_XX";
static string _DictContent = "_DictContent";
static string _Type = "_Type";
static string _DataClassFields = "_DataClassFields";
private static void ParseExcelToCS(List<List<string>> cellInfo, string tableName, string ExportFileName, string ExportPath)
{
//读取模板文件
TextAsset template = AssetDatabase.LoadAssetAtPath<TextAsset>("Assets/Editor/Excel/Template.txt");
StringBuilder templateStr = new StringBuilder(template.text);
string saveFullPath = exportDirPath + "Data_" + tableName + ".cs";//导出文件的路径
if (File.Exists(saveFullPath))//存在文件,删除
File.Delete(saveFullPath);
List<string> oneRowList = cellInfo[0];//中文备注
List<string> twoRowList = cellInfo[1];//字段名
List<string> threeRowList = cellInfo[2];//字段类型
string DataClassName = "Data_" + tableName + "Class";
templateStr.Replace(_Data_XXDict, "Data_" + tableName);
templateStr.Replace(_DataClassName, DataClassName);//数据的类
StringBuilder dataClassFields = new StringBuilder();
StringBuilder dataClassParameter = new StringBuilder();
StringBuilder dataClassFun = new StringBuilder();
List<int> vaildColumIndex = new List<int>();
for (int i = 0; i < oneRowList.Count; i++)//循环第一行
{
if (oneRowList[i].StartsWith(IgnoreChar)) continue;
if (twoRowList[i].StartsWith(IgnoreChar)) continue;
vaildColumIndex.Add(i);//将过滤有效的列加入一个数组
//下面为该下面部分要输出的结果
///
/// id
///
//public int id;
dataClassFields.AppendFormat("\t/// \r\n\t/// {0}\r\n\t/// \r\n", oneRowList[i]);//写入备注
dataClassFields.AppendFormat("\tpublic {0} {1};\r\n", threeRowList[i], twoRowList[i]);//public 类型 字段名
//public Data_TestClass(int id,string name)
//{
// this.id = id;
// this.name = name;
//}
dataClassParameter.AppendFormat("{0} {1}", threeRowList[i], twoRowList[i]);//构造函数
if (i < oneRowList.Count - 1)
dataClassParameter.Append(",");
dataClassFun.AppendFormat("\t\tthis.{0} = {1};", twoRowList[i], twoRowList[i]);
if (i < oneRowList.Count - 1)
dataClassFun.Append("\r\n");
}
templateStr.Replace(_DataClassFields, dataClassFields.ToString());
templateStr.Replace(_DataClassParameter, dataClassParameter.ToString());
templateStr.Replace(_DataClassFun, dataClassFun.ToString());
StringBuilder rowData = new StringBuilder();
string _type = null;
for (int i = 3; i < cellInfo.Count; i++)//从第3行开始写入数据
{
List<string> RowInfo = cellInfo[i];
string id = null;
if (threeRowList[0] == "string")//类型是字符串还是int
{
id = "\"" + RowInfo[0] + "\"";
}
else
{
id = RowInfo[0];
}
StringBuilder inner = new StringBuilder();
for (int j = 0; j < vaildColumIndex.Count; j++)//遍历每一行每一列
{
int ColumIndex = vaildColumIndex[j];//提取有效的索引,如ColumIndex不是123456,是1345
string cell = RowInfo[ColumIndex];
string FieldName = twoRowList[ColumIndex];
if (ColumIndex == 0)
{
_type = threeRowList[ColumIndex];
}
string FieldType = threeRowList[ColumIndex];
cell = AllToString(cell, FieldType);
inner.Append(cell);
if (j < vaildColumIndex.Count - 1) inner.Append(",");
}
rowData.Append("\t\t{");
rowData.AppendFormat("{0} ,new {1}({2})", id, DataClassName, inner);
rowData.Append("},");
//public static Dictionary data = new Dictionary()
//{
//{1 ,new Data_TestClass(1,"name")},
//}
if (i < cellInfo.Count - 1) rowData.Append("\r\n");//最后一行不用换行
}
templateStr.Replace(_DictContent, rowData.ToString());
templateStr.Replace(_Type, _type);
using (StreamWriter sw = new StreamWriter(File.Create(saveFullPath)))
{
sw.Write(templateStr.ToString());//写入最后数据到cs文件
sw.Flush();
sw.Close();
}
}
AllToString 将excel单元格转换为可以实例化的格式,如(1,2,3)=>new Vector3(1,2,3)
private static string AllToString(string cell, string type)
{
StringBuilder result = new StringBuilder();
switch (type)
{
case "int":
if (cell.Length <= 0) return "0";
result.Append(cell);
break;
case "int[]":
if (cell.Length <= 0) return "new int[] { }";
result.Append("new int[] {");
result.Append(cell);
result.Append("}");
break;
case "int[][]":
if (cell.Length <= 0) return "new int[][] { }";
result.Append("new int[][] {");
string[] s = cell.Split(',');
for (int i = 0; i < s.Length; i++)
{
result.Append("new int[]" + s[i]);
if (i < s.Length - 1)
{
result.Append(",");
}
}
//result.Append(cell);
result.Append("}");
break;
case "string":
if (cell.Length <= 0) return "null";
result.Append("\"");
result.Append(cell);
result.Append("\"");
break;
case "string[]"://支持"111","222"和111;222
if (cell.Length <= 0) return "null";
result.Append("new string[] {");
if (cell.IndexOf(";") < 0 && cell.IndexOf("\"") < 0)
{
result.Append("\"");
result.Append(cell);//"aaa"=>new string[]{"aaa"}
result.Append("\"");
}
else
{
if (cell.IndexOf(";") > 0)
{
string[] str = cell.Split(';');
for (int i = 0; i < str.Length; i++)
{
result.Append("\"");
result.Append(str[i]);//"aaa;bbb"=>new string[] {"aaa","bbb"}
result.Append("\"");
if (i < str.Length)
{
result.Append(",");
}
}
}
else
{
result.Append(cell);//"aaa","bbb"=>new string[] {"aaa","bbb"}
}
}
result.Append("}");
break;
break;
case "float":
if (cell.Length <= 0) return "0f";
result.AppendFormat("{0}f", cell);
break;
case "double":
if (cell.Length <= 0) return "0d";
result.AppendFormat("{0}d", cell);
break;
case "bool":
if (cell.Length <= 0) return "false";
result.Append(cell.ToLower());
break;
case "Vector3":
if (cell.Length <= 0) return "null";
result.AppendFormat("new Vector3{0}", cell);
break;
case "Vector3[]":
StringBuilder sb = new StringBuilder();
if (cell.Length <= 3) return "null";
string[] strings = cell.Split(new char[] { '(', ')' }, System.StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < strings.Length; i++)
{
if (strings[i].Length <= 1) continue;
sb.Append("new Vector3");
sb.Append("(");
sb.AppendFormat("{0}", strings[i]);
sb.Append(")");
if (i < strings.Length - 1)
sb.Append(",");
}
result.Append("new Vector3[]");
result.Append("{");
result.AppendFormat("{0}", sb);
result.Append("}");
break;
}
return result.ToString();
}
怎么使用
Data_Test.data,Data_Test.data[id]访问
//下面是导出关键类
public static class Data_Test
{
public static Dictionary
{
{ 1, new Data_TestClass(1, “name”) }
}
}
Excel文件导出cs文件就讲解到这里,如果上面有代码不理解或者有问题,可以在评论区留言