Unity 读Excel,读取xlsx文件解决方案

Unity读取表格数据

效果:

思路:

Unity可以解析Json,但是读取Excel需要插件的帮助,那就把这个功能分离开,读表插件就只管读表转Json,Unity就只管Json解析,中间需要一个存储空间,使用ScriptableObject数据类是个不错的选择。
缺点也很明显,我们只能在Unity编辑器模式下使用这个工具,也就是无法在打包后读取Excel,不过我们再也不用担心读表插件出问题了,因为打包后根本用不到读表插件。
实现步骤:
  • 步骤一:Excel数据转换成Json数据
  • 步骤二:将数据存储到ScriptableObject持久类中
  • 步骤三:Unity读取ScriptableObject类

Excel数据转换成Json数据

因为只考虑在Unity编辑器模式使用,所以直接写一个编辑器窗口就行了
主要功能概述
  • 用户界面(EditorWindow):提供一个界面让用户选择 Excel 文件,并进行相应的操作,如选择是否生成 C# 类文件、开始转换等。
  • 文件处理:用户可以拖拽文件或文件夹到指定区域,程序会识别路径并加载 Excel 文件列表。
  • 转换操作:Excel 文件被读取,转换为 JSON 数据,并保存到指定路径。
  1. 打开编辑窗口
javascript 复制代码
[MenuItem("Tools/ExcelToJson")]
public static void ShowWindow()
{
    ExcelToUnityWindow window = GetWindow<ExcelToUnityWindow>("Excel 转 Json 工具");
    window.minSize = new Vector2(400 , 300);
}

这段代码创建了一个编辑器窗口,当用户点击 Unity 编辑器菜单中的 "Tools/ExcelToJson" 时,会弹出 ExcelToUnityWindow 窗口。

  1. 初始化并加载 JsonFileData
javascript 复制代码
private void OnEnable()
{
    csOutputPath = Path.Combine(Application.dataPath , "Tool/ExcelTool/ConfigData");
    jsonFileCollector = Resources.Load<JsonFileData>("JsonFileCollector");
    if (jsonFileCollector == null)
        Debug.LogError("未找到 JsonFileCollector 实例,请确保已创建该资产文件!");
    RefreshFileList();
}

OnEnable 方法会在编辑器窗口启用时调用,它会加载 JsonFileCollector 实例,这个实例用于管理 JSON 数据。

  1. 文件夹路径选择与拖拽支持
javascript 复制代码
private void HandleDragAndDrop(Rect dropArea)
{
    Event evt = Event.current;
    if (evt.type == EventType.DragUpdated || evt.type == EventType.DragPerform)
    {
        if (dropArea.Contains(evt.mousePosition))
        {
            DragAndDrop.visualMode = DragAndDropVisualMode.Copy;

            if (evt.type == EventType.DragPerform)
            {
                DragAndDrop.AcceptDrag();
                if (DragAndDrop.paths.Length > 0)
                {
                    string draggedPath = DragAndDrop.paths[0];
                    if (File.Exists(draggedPath) && draggedPath.EndsWith(".xlsx"))
                    {
                        folderPath = Path.GetDirectoryName(draggedPath);
                    }
                    else if (Directory.Exists(draggedPath))
                    {
                        folderPath = draggedPath;
                    }
                    RefreshFileList();
                }

                evt.Use();
            }
        }
    }
}

这个方法实现了拖拽功能,用户可以将文件或文件夹拖拽到指定区域,程序会检测并更新路径。

  1. 转换文件的核心操作
javascript 复制代码
private void ConvertExcelFiles()
{
    foreach (string filePath in selectedExcelFiles)
    {
        ParseFile(filePath , createCS , csOutputPath);
    }

    EditorUtility.DisplayDialog("完成" , "所有 Excel 文件已成功转换!" , "确定");
}

ConvertExcelFiles() 方法会遍历用户选择的 Excel 文件,并调用 ParseFile() 方法来处理每一个文件。这个方法的核心功能是将 Excel 文件转换为 JSON 格式。

  1. Excel 文件解析与 JSON 转换
javascript 复制代码
private static string ParseFile(string path , bool createCS , string csOutputPath)
{
    if (!path.EndsWith("xlsx")) return null;

    string tableName = "";
    string cfgName = "";
    Excel excel = null;

    try
    {
        (Excel a, string b) temp = ExcelHelper.LoadExcel(path);
        excel = temp.a;
        cfgName = temp.b;

        StringBuilder sb = new StringBuilder();
        JsonWriter writer = new JsonWriter(sb);
        writer.WriteObjectStart();

        foreach (ExcelTable table in excel.Tables)
        {
            tableName = table.TableName;
            if (tableName.StartsWith("#")) continue;

            if (createCS)
            {
                try
                {
                    Debug.Log($"生成 C# 类文件:{csOutputPath}");
                    ExcelDeserializer deserializer = new ExcelDeserializer
                    {
                        FieldNameLine = 1,
                        FieldTypeLine = 2,
                        FieldValueLine = 3,
                        IgnoreSymbol = "#",
                        ModelPath = $"{Application.dataPath}/Tool/ExcelTool/Editor/Excel/ExcelToUnity/DataItem.txt"
                    };
                    deserializer.GenerateCS(table, cfgName, csOutputPath);  // 生成 C# 文件
                }
                catch (System.Exception ex)
                {
                    Debug.LogError($"生成 C# 类文件时出错: {ex.Message}");
                    return null;
                }
            }

            writer.WritePropertyName("Config");
            writer.WriteArrayStart();

            for (int i = 4; i <= table.NumberOfRows; i++)
            {
                string idStr = table.GetValue(i, 1)?.ToString();
                if (string.IsNullOrEmpty(idStr)) break;

                writer.WriteObjectStart();

                for (int j = 1; j <= table.NumberOfColumns; j++)
                {
                    string propName = table.GetValue(1, j)?.ToString()?.Replace("*", "");
                    string propType = table.GetValue(2, j)?.ToString();

                    if (string.IsNullOrEmpty(propName) || propName.StartsWith("#")) continue;

                    writer.WritePropertyName(propName);
                    string value = table.GetValue(i, j)?.ToString();

                    if (propType == "int")
                    {
                        writer.Write(int.TryParse(value, out int intValue) ? intValue : 0);
                    }
                    else if (propType == "bool")
                    {
                        writer.Write(value == "1" || value.ToLower() == "true");
                    }
                    else if (propType == "float")
                    {
                        writer.Write(float.TryParse(value, out float floatValue) ? floatValue : 0);
                    }
                    else
                    {
                        writer.Write(value);
                    }
                }

                writer.WriteObjectEnd();
            }

            writer.WriteArrayEnd();
        }

        writer.WriteObjectEnd();

        string outputDir = Path.Combine(Application.streamingAssetsPath, "DataFiles");
        if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);

        string outputPath = Path.Combine(outputDir, Path.GetFileNameWithoutExtension(path) + ".json");
        File.WriteAllText(outputPath, sb.ToString());

        Debug.Log("转换成功!路径:" + outputPath);
        return sb.ToString();
    }
    catch (System.Exception ex)
    {
        Debug.LogError($"转换文件 {path} 时出错: {ex.Message}");
        return null;
    }
}

这是处理 Excel 转 JSON 的关键方法。主要流程如下:

  • 加载 Excel 文件:使用 ExcelHelper.LoadExcel() 方法加载 Excel 文件。
  • 遍历表格:对于每个表格,程序会遍历表格中的行和列,将数据转换成适当类型(int, bool, float, string)。
  • 生成 JSON:利用 JsonWriter 将表格中的数据按格式写入 JSON 格式。
  • 保存文件:生成的 JSON 数据被保存到 DataFiles 文件夹中。

将数据存储到ScriptableObject持久类中。

准备存储Json数据
javascript 复制代码
    [System.Serializable]
    public class JsonData
    {
        [HideInInspector]
        public string FileName;
        public string Parent;
        public string Title;

        public JsonData(string fileName , string parent , string title)
        {
            FileName = fileName;
            Parent = parent;
            Title = title;
        }
    }

    [System.Serializable]
    public class ConfigData
    {
        public string configName;
        public int propertyCount;
        public string data;

        public ConfigData(string configName , int propertyCount)
        {
            this.configName = configName;
            this.propertyCount = propertyCount;
        }
    }
    [Header("JSON 文件信息列表")]
    [Tooltip("每个 JSON 文件的 Json数据")]
    public List<ConfigData> jsonDataList = new List<ConfigData>();
    [Tooltip("每个 JSON 文件的 Parent 和 Title 字段")]
    public List<JsonData> jsonPropertyList = new List<JsonData>();
首先遍历目标路径,查找所有 JSON 文件并解析其内容
javascript 复制代码
public void RefreshJsonFileList()
{
    jsonDataList.Clear();
    jsonPropertyList.Clear(); // 清空列表

    if (!Directory.Exists(dataFilesPath))
    {
        Debug.LogError($"目标路径不存在: {dataFilesPath}");
        return;
    }

    string[] files = Directory.GetFiles(dataFilesPath, "*.json");
    if (files.Length == 0)
    {
        Debug.LogWarning("未找到任何 JSON 文件.");
    }

    foreach (var file in files)
    {
        string fileName = Path.GetFileNameWithoutExtension(file);
        int propertyCount = GetJsonFilePropertyCount(file);
        jsonDataList.Add(new ConfigData(fileName, propertyCount));
        ReadJsonFile(file, fileName);
    }

    Debug.Log($"找到 {jsonDataList.Count} 个 JSON 文件.");
}
  • Directory.GetFiles 查找文件夹中的 .json 文件。
  • GetJsonFilePropertyCount 获取每个文件中 Config 数组中第一个对象的属性数量。
  • ReadJsonFile 解析 JSON 文件,提取 Parent 和 Title 信息。
读取 JSON 文件内容
javascript 复制代码
public void ReadJsonFile(string file, string fileName)
{
    string jsonContent = File.ReadAllText(file);

    JObject jsonObject = null;
    try
    {
        jsonObject = JObject.Parse(jsonContent);
    }
    catch (System.Exception ex)
    {
        Debug.LogError($"解析文件失败: {file}, 错误: {ex.Message}");
        return;
    }

    JArray configArray = jsonObject["Config"] as JArray;

    if (configArray != null)
    {
        foreach (var configItem in configArray)
        {
            string parent = configItem["Parent"]?.ToString();
            string title = configItem["Title"]?.ToString();

            if (!string.IsNullOrEmpty(parent) && !string.IsNullOrEmpty(title))
            {
                jsonPropertyList.Add(new JsonData(fileName, parent, title));
            }
        }
    }
}
  • 使用 File.ReadAllText 读取文件内容。
  • 通过 JObject.Parse 解析 JSON 字符串。
  • 查找 Config 数组中的每个元素,提取 Parent 和 Title,并将其与文件名一起保存为 JsonData 对象。
获取 JSON 文件的属性数量,方便分类
javascript 复制代码
int GetJsonFilePropertyCount(string file)
{
    string jsonContent = File.ReadAllText(file);

    JObject jsonObject = null;
    try
    {
        jsonObject = JObject.Parse(jsonContent);
    }
    catch (System.Exception ex)
    {
        Debug.LogError($"解析文件失败: {file}, 错误: {ex.Message}");
        return 0;
    }

    JArray configArray = jsonObject["Config"] as JArray;
    if (configArray == null || configArray.Count == 0)
    {
        Debug.LogWarning($"文件 {file} 中没有找到有效的 'Config' 数组。");
        return 0;
    }

    return configArray[0].Children<JProperty>().Count();
}
  • 读取 JSON 文件并解析为 JObject。
  • 获取 Config 数组的第一个元素,并统计其属性数量。
加载和保存 JSON 数据
  • 加载 JSON 数据,GetJsonData() 和 LoadConfig() 方法批量加载 JSON 数据:
javascript 复制代码
public void GetJsonData()
{
#if UNITY_EDITOR
    cfgProgress = 0;
    string filepath = ConfigManager.GetStreamingAssetsPath();
    foreach (var item in jsonDataList)
    {
        string configPath = $"DataFiles/{item.configName}.json";
        LoadConfig(item, configPath, filepath, jsonDataList.Count);
    }
#endif
}

void LoadConfig(ConfigData configData, string configPath, string filepath, int Count, Action OnConfigLoaded = null)
{
#if UNITY_EDITOR
    string filePath = Path.Combine(filepath, configPath);
    try
    {
        if (File.Exists(filePath))
        {
            string fileContent = File.ReadAllText(filePath);
            configData.data = fileContent;
            cfgProgress++;
            if (cfgProgress == Count)
            {
                OnConfigLoaded?.Invoke();
            }
        }
        else
        {
            Debug.LogError($"文件不存在: {filePath}");
        }
    }
    catch (Exception ex)
    {
        Debug.LogError($"加载配置表时发生错误: {ex.Message}");
    }
#endif
}
  • 保存实例
javascript 复制代码
public void SaveInstance()
{
#if UNITY_EDITOR
    EditorUtility.SetDirty(this); // 标记为已修改
    AssetDatabase.SaveAssets();   // 保存所有修改
    AssetDatabase.Refresh();      // 刷新数据库
    Debug.Log("JsonFileCollector 实例已保存.");
#endif
}
做一个编辑器面板用于手动读取保存数据
javascript 复制代码
[CustomEditor(typeof(JsonFileData))]
public class JsonFileCollectorEditor : Editor
{
    public override void OnInspectorGUI()
    {
        JsonFileData collector = (JsonFileData)target;

        DrawDefaultInspector();

        if (GUILayout.Button("刷新文件列表", GUILayout.Height(30)))
        {
            collector.RefreshJsonFileList();
            collector.GetJsonData();
            collector.SaveInstance();
        }
        GUILayout.Space(10);
        if (collector.jsonDataList.Count == 0)
        {
            GUILayout.Label("未找到 JSON 文件.");
        }
    }
}

OK,大概这样子

Unity读取ScriptableObject类

解析 JSON 数据

加载完 JSON 文件后,你通常需要将字符串类型的 JSON 数据转换成实际的 C# 对象。这个过程通常依赖于第三方库(如 Newtonsoft.Json 或 LitJson)来解析。

可以通过 JsonMapper.ToObject 方法(来自 LitJson 库)将 JSON 字符串转换为指定类型的对象。

javascript 复制代码
	public List<T> GetConfigRoot<T>()
	{
	    foreach (var item in jsonDataList)
	    {
	        if (item.configName == typeof(T).Name)
	        {
	            ConfigRoot<T> configRoot = JsonMapper.ToObject<ConfigRoot<T>>(item.data); // 解析 JSON 数据为对象
	            return configRoot.Config;
	        }
	    }
	    return null;
	}
    public List<T> GetConfigRoot<T>(string name)
    {
        foreach (var item in jsonDataList)
        {
            if (item.configName == name)
            {
                ConfigRoot<T> configRoot = JsonMapper.ToObject<ConfigRoot<T>>(item.data);
                return configRoot.Config;
            }
        }
        return null;
    }

通过上述步骤,Unity 使用存储好的 JSON 数据的流程如下:

  • 存储 JSON 数据:通过 ScriptableObject(如 JsonFileData)将 JSON 文件的数据存储在 ConfigData.data 字段中。
  • 加载 JSON 数据:通过 GetJsonData() 和 LoadConfig() 方法从磁盘读取 JSON 文件内容,并存储到内存中。
  • 解析 JSON 数据:使用第三方库(如 LitJson)将 JSON 字符串转换为 C# 对象,通常通过 JsonMapper.ToObject() 方法进行。
  • 使用数据:将解析后的数据应用到游戏逻辑中,如配置角色、武器、敌人等游戏元素。

通过这种方式,你能够在 Unity 中灵活地管理和使用 JSON 配置数据。

如何使用

一般来说需要提供一个数据类,一个表的名称就可以拿到里面的数据

例如:

javascript 复制代码
JsonFileData.GetConfigRoot<数据类>(文件名)

不过如果有多张表,相同的结构该怎么办?

我们可以使用之前存储好的表名来遍历里面的数据,使用属性数量来过滤出我们使用的数据

属性数量8:

属性数量6:

过滤出数据:

javascript 复制代码
foreach (var item in JsonFileData.jsonDataList)
{
    if (item.propertyCount==6)
    {
        foreach (var config in JsonFileData.GetConfigRoot<Config_Default>(item.configName))
        {
            Config_Default Default = config;
            string info = "";
            info += "ID:" + Default.ID;
            info += ",父节点:" + Default.Parent;
            info += ",标题:" + Default.Title;
            info += ",内容:" + Default.Content;
            info += ",图片路径:" + Default.SpritePath;
            info += ",视频路径:" + Default.VideoPath;
            Debug.Log(info);
        }
    }
    else
    {
        foreach (var config in ConfigHelper.GetConfigInfo<Config_Answer>(item.configName))
        {
            Config_Answer Default = config;
            string info = "";
            info += "ID:" + Default.ID;
            info += ",父节点:" + Default.Parent;
            info += ",标题:" + Default.Title;
            info += ",内容:" + Default.OptionA;
            info += "," + Default.OptionB;
            info += "," + Default.OptionC;
            info += "," + Default.OptionD;
            info += ",答案:" + Default.CorrectAnswer;
            Debug.Log(info);
        }
    }
}

使用属性数量过滤不是一个好的选择,各位可以各显神通,看看如何解决这个问题。

Demo下载链接👉 读表工具

相关推荐
大G哥4 小时前
pytest自动化测试数据驱动yaml/excel/csv/json
json·excel·pytest
小奥超人4 小时前
Excel粘贴复制不完整的原因以及解决方法
windows·经验分享·microsoft·excel·办公技巧
code04号4 小时前
python脚本:批量提取excel数据
开发语言·python·excel
Unity_RAIN4 小时前
Unity 战斗系统中角色UI血条设计
ui·unity·游戏引擎
m0_748255266 小时前
vue3导入excel并解析excel数据渲染到表格中,纯前端实现。
前端·excel
左漫在成长7 小时前
王佩丰24节Excel学习笔记——第十九讲:Indirect函数
笔记·学习·excel
Just_Paranoid7 小时前
解析 Java 项目生成常量、变量和函数 Excel 文档
java·python·正则表达式·excel·javadoc
先生沉默先8 小时前
unity使用代码在动画片段中添加event
unity
H4_xy9 小时前
Excel中match()函数
excel
左漫在成长9 小时前
王佩丰24节Excel学习笔记——第十八讲:Lookup和数组
笔记·学习·excel