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 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 ParseFieldInfo(IRow nameRow, IRow typeRow) { List fields = new List(); 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"; case "floatlist": return "List"; case "boollist": return "List"; case "stringlist": return "List"; default: return "string"; } } private static string GenerateRowClass(List 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 fields = ParseFieldInfo(nameRow, typeRow); // 创建容器实例 ScriptableObject container = ScriptableObject.CreateInstance(containerType); if (container == null) { Debug.LogError($"无法创建容器实例: {containerClassName}"); return; } // 获取 items 字段并创建 List 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(value, int.Parse); case "floatlist": return ParseList(value, float.Parse); case "boollist": return ParseList(value, bool.Parse); case "stringlist": return ParseList(value, s => s); default: return value; } } catch (Exception e) { Debug.LogWarning($"转换值 '{value}' 到类型 {rawType} 失败: {e.Message},使用默认值。"); return GetDefaultValue(rawType); } } private static List ParseList(string value, Func parser) { List list = new List(); 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(); case "floatlist": return new List(); case "boollist": return new List(); case "stringlist": return new List(); default: return null; } } private class FieldInfo { public string Name; public string RawType; // Excel中的原始类型标识 public string Type; // 映射后的C#类型字符串 } } public static class ExcelBatchLoader { public static Dictionary> LoadAllXlsxFiles(string directoryPath, SearchOption searchOption = SearchOption.TopDirectoryOnly) { var result = new Dictionary>(); 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 sheets = new List(); 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; } }