添加 Test2.txt
This commit is contained in:
366
Test2.txt
Normal file
366
Test2.txt
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using NPOI.SS.UserModel;
|
||||||
|
using NPOI.XSSF.UserModel;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.Compilation;
|
||||||
|
|
||||||
|
public class ExcelToEntityGenerator : EditorWindow
|
||||||
|
{
|
||||||
|
private static string ExcelFolder = "Assets/Data/Excels";
|
||||||
|
private static string ScriptsFolder = "Assets/Scripts/Data";
|
||||||
|
private static string AssetFolder = "Assets/Data/Containers";
|
||||||
|
|
||||||
|
public static void LoadAll(string excelFolder, string scriptsFolder, string assetFolder)
|
||||||
|
{
|
||||||
|
ExcelFolder = excelFolder;
|
||||||
|
ScriptsFolder = scriptsFolder;
|
||||||
|
AssetFolder = assetFolder;
|
||||||
|
// 确保输出目录存在
|
||||||
|
if (!Directory.Exists(ScriptsFolder)) Directory.CreateDirectory(ScriptsFolder);
|
||||||
|
if (!Directory.Exists(AssetFolder)) Directory.CreateDirectory(AssetFolder);
|
||||||
|
|
||||||
|
// 加载所有 Excel 文件
|
||||||
|
var allData = ExcelBatchLoader.LoadAllXlsxFiles(ExcelFolder);
|
||||||
|
if (allData.Count == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("未找到任何 .xlsx 文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为每个文件生成数据行类和容器类
|
||||||
|
foreach (var kv in allData)
|
||||||
|
{
|
||||||
|
string fileName = kv.Key; // 不含扩展名
|
||||||
|
var sheet = kv.Value[0]; // 只处理第一个工作表
|
||||||
|
|
||||||
|
// 解析字段信息
|
||||||
|
IRow nameRow = sheet.GetRow(0);
|
||||||
|
IRow typeRow = sheet.GetRow(1);
|
||||||
|
List<FieldInfo> fields = ParseFieldInfo(nameRow, typeRow);
|
||||||
|
|
||||||
|
// 生成数据行类
|
||||||
|
string rowClassPath = $"{ScriptsFolder}/{fileName}Container.cs";
|
||||||
|
var rowClassContent = GenerateRowClass(fields, fileName, rowClassPath);
|
||||||
|
|
||||||
|
// 生成容器类
|
||||||
|
string containerClassPath = $"{ScriptsFolder}/{fileName}Container.cs";
|
||||||
|
GenerateContainerClass(fileName, containerClassPath, rowClassContent);
|
||||||
|
}
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateAllContainers()
|
||||||
|
{
|
||||||
|
// 重新加载 Excel 文件(此时数据行类已编译)
|
||||||
|
var allData = ExcelBatchLoader.LoadAllXlsxFiles(ExcelFolder);
|
||||||
|
foreach (var kv in allData)
|
||||||
|
{
|
||||||
|
string fileName = kv.Key;
|
||||||
|
var sheet = kv.Value[0];
|
||||||
|
CreateContainerFromSheet(sheet, fileName);
|
||||||
|
}
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
Debug.Log("所有 ScriptableObject 容器已生成完毕");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<FieldInfo> ParseFieldInfo(IRow nameRow, IRow typeRow)
|
||||||
|
{
|
||||||
|
List<FieldInfo> fields = new List<FieldInfo>();
|
||||||
|
for (int i = 0; i < nameRow.LastCellNum; i++)
|
||||||
|
{
|
||||||
|
string rawName = nameRow.GetCell(i)?.ToString()?.Trim();
|
||||||
|
string rawType = typeRow.GetCell(i)?.ToString()?.Trim();
|
||||||
|
if (string.IsNullOrEmpty(rawName) || string.IsNullOrEmpty(rawType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string fieldName = SanitizeName(rawName);
|
||||||
|
string fieldType = MapType(rawType); // 映射为C#类型字符串
|
||||||
|
fields.Add(new FieldInfo { Name = fieldName, RawType = rawType, Type = fieldType });
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SanitizeName(string name)
|
||||||
|
{
|
||||||
|
// 移除非法字符,并确保首字母大写
|
||||||
|
string sanitized = Regex.Replace(name, @"[^a-zA-Z0-9_]", "");
|
||||||
|
if (string.IsNullOrEmpty(sanitized)) return "Field";
|
||||||
|
return char.ToUpper(sanitized[0]) + sanitized.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MapType(string typeStr)
|
||||||
|
{
|
||||||
|
switch (typeStr.ToLower())
|
||||||
|
{
|
||||||
|
case "int": return "int";
|
||||||
|
case "float": return "float";
|
||||||
|
case "bool": return "bool";
|
||||||
|
case "string": return "string";
|
||||||
|
case "intlist": return "List<int>";
|
||||||
|
case "floatlist": return "List<float>";
|
||||||
|
case "boollist": return "List<bool>";
|
||||||
|
case "stringlist": return "List<string>";
|
||||||
|
default: return "string";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateRowClass(List<FieldInfo> fields, string className, string outputPath)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.AppendLine("//由程序自动生成");
|
||||||
|
sb.AppendLine("using System;");
|
||||||
|
sb.AppendLine("using System.Collections.Generic;");
|
||||||
|
sb.AppendLine("using UnityEngine;");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("[System.Serializable]");
|
||||||
|
sb.AppendLine($"public class {className}");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
sb.AppendLine($" public {field.Type} {field.Name};");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
// WriteFile(outputPath, sb.ToString());
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GenerateContainerClass(string rowClassName, string outputPath, string rowClassContent)
|
||||||
|
{
|
||||||
|
|
||||||
|
string containerName = rowClassName + "Container";
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
// sb.AppendLine("using System.Collections.Generic;");
|
||||||
|
// sb.AppendLine("using UnityEngine;");
|
||||||
|
sb.AppendLine(rowClassContent);
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("[CreateAssetMenu(fileName = \"" + containerName + "\", menuName = \"Data/" + containerName + "\")]");
|
||||||
|
sb.AppendLine($"public class {containerName} : ScriptableObject");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
sb.AppendLine($" public List<{rowClassName}> items;");
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
WriteFile(outputPath, sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteFile(string path, string content)
|
||||||
|
{
|
||||||
|
// 如果文件已存在,删除后重新创建(避免残留)
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
File.WriteAllText(path, content, Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreateContainerFromSheet(ISheet sheet, string fileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 获取已编译的类型
|
||||||
|
string rowClassName = fileName;
|
||||||
|
string containerClassName = fileName + "Container";
|
||||||
|
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load("Assembly-CSharp");
|
||||||
|
Type rowType = assembly.GetType(rowClassName);
|
||||||
|
Type containerType = assembly.GetType(containerClassName);
|
||||||
|
if (rowType == null || containerType == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"无法找到类型: {rowClassName} 或 {containerClassName},请检查编译是否成功");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析字段信息(与生成类时一致)
|
||||||
|
IRow nameRow = sheet.GetRow(0);
|
||||||
|
IRow typeRow = sheet.GetRow(1);
|
||||||
|
List<FieldInfo> fields = ParseFieldInfo(nameRow, typeRow);
|
||||||
|
|
||||||
|
// 创建容器实例
|
||||||
|
ScriptableObject container = ScriptableObject.CreateInstance(containerType);
|
||||||
|
if (container == null)
|
||||||
|
{
|
||||||
|
Debug.LogError($"无法创建容器实例: {containerClassName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 items 字段并创建 List<T>
|
||||||
|
System.Reflection.FieldInfo itemsField = containerType.GetField("items");
|
||||||
|
Type listType = typeof(List<>).MakeGenericType(rowType);
|
||||||
|
object listInstance = Activator.CreateInstance(listType);
|
||||||
|
|
||||||
|
// 遍历数据行(从第3行开始,索引2)
|
||||||
|
for (int rowIdx = 2; rowIdx <= sheet.LastRowNum; rowIdx++)
|
||||||
|
{
|
||||||
|
IRow dataRow = sheet.GetRow(rowIdx);
|
||||||
|
if (dataRow == null) continue;
|
||||||
|
|
||||||
|
// 创建数据行对象
|
||||||
|
object rowObj = Activator.CreateInstance(rowType);
|
||||||
|
|
||||||
|
bool hasData = false; // 标记该行是否有有效数据
|
||||||
|
for (int i = 0; i < fields.Count; i++)
|
||||||
|
{
|
||||||
|
var field = fields[i];
|
||||||
|
var cell = dataRow.GetCell(i);
|
||||||
|
string cellValue = cell?.ToString()?.Trim();
|
||||||
|
if (string.IsNullOrEmpty(cellValue)) continue;
|
||||||
|
|
||||||
|
hasData = true;
|
||||||
|
// 转换并赋值
|
||||||
|
object convertedValue = ConvertValue(cellValue, field.RawType);
|
||||||
|
System.Reflection.FieldInfo rowField = rowType.GetField(field.Name);
|
||||||
|
if (rowField != null)
|
||||||
|
{
|
||||||
|
rowField.SetValue(rowObj, convertedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasData)
|
||||||
|
{
|
||||||
|
// 添加到列表
|
||||||
|
listType.GetMethod("Add").Invoke(listInstance, new[] { rowObj });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将列表赋值给容器
|
||||||
|
itemsField.SetValue(container, listInstance);
|
||||||
|
|
||||||
|
// 保存资产
|
||||||
|
string assetPath = $"{AssetFolder}/{fileName}Container.asset";
|
||||||
|
// 如果已存在,先删除
|
||||||
|
if (File.Exists(assetPath))
|
||||||
|
{
|
||||||
|
AssetDatabase.DeleteAsset(assetPath);
|
||||||
|
}
|
||||||
|
AssetDatabase.CreateAsset(container, assetPath);
|
||||||
|
Debug.Log($"生成容器资产: {assetPath}");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"处理文件 {fileName} 时出错: {e.Message}\n{e.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ConvertValue(string value, string rawType)
|
||||||
|
{
|
||||||
|
string lowerType = rawType.ToLower();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (lowerType)
|
||||||
|
{
|
||||||
|
case "int":
|
||||||
|
return int.Parse(value);
|
||||||
|
case "float":
|
||||||
|
return float.Parse(value);
|
||||||
|
case "bool":
|
||||||
|
return bool.Parse(value);
|
||||||
|
case "string":
|
||||||
|
return value;
|
||||||
|
case "intlist":
|
||||||
|
return ParseList<int>(value, int.Parse);
|
||||||
|
case "floatlist":
|
||||||
|
return ParseList<float>(value, float.Parse);
|
||||||
|
case "boollist":
|
||||||
|
return ParseList<bool>(value, bool.Parse);
|
||||||
|
case "stringlist":
|
||||||
|
return ParseList<string>(value, s => s);
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"转换值 '{value}' 到类型 {rawType} 失败: {e.Message},使用默认值。");
|
||||||
|
return GetDefaultValue(rawType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<T> ParseList<T>(string value, Func<string, T> parser)
|
||||||
|
{
|
||||||
|
List<T> list = new List<T>();
|
||||||
|
if (string.IsNullOrEmpty(value)) return list;
|
||||||
|
string[] parts = value.Split(new[] { '#' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (string part in parts)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
list.Add(parser(part.Trim()));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略解析失败的项
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object GetDefaultValue(string rawType)
|
||||||
|
{
|
||||||
|
switch (rawType.ToLower())
|
||||||
|
{
|
||||||
|
case "int": return 0;
|
||||||
|
case "float": return 0f;
|
||||||
|
case "bool": return false;
|
||||||
|
case "string": return "";
|
||||||
|
case "intlist": return new List<int>();
|
||||||
|
case "floatlist": return new List<float>();
|
||||||
|
case "boollist": return new List<bool>();
|
||||||
|
case "stringlist": return new List<string>();
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FieldInfo
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public string RawType; // Excel中的原始类型标识
|
||||||
|
public string Type; // 映射后的C#类型字符串
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExcelBatchLoader
|
||||||
|
{
|
||||||
|
public static Dictionary<string, List<ISheet>> LoadAllXlsxFiles(string directoryPath, SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, List<ISheet>>();
|
||||||
|
|
||||||
|
if (!Directory.Exists(directoryPath))
|
||||||
|
{
|
||||||
|
Debug.LogError($"目录不存在: {directoryPath}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] files = Directory.GetFiles(directoryPath, "*.xlsx", searchOption);
|
||||||
|
foreach (string filePath in files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||||
|
{
|
||||||
|
IWorkbook workbook = new XSSFWorkbook(fs);
|
||||||
|
List<ISheet> sheets = new List<ISheet>();
|
||||||
|
for (int i = 0; i < workbook.NumberOfSheets; i++)
|
||||||
|
{
|
||||||
|
sheets.Add(workbook.GetSheetAt(i));
|
||||||
|
}
|
||||||
|
var fileName = Path.GetFileNameWithoutExtension(filePath);
|
||||||
|
result[fileName] = sheets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"加载文件失败: {filePath}\n{e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user