Unity读取表格数据
效果:
思路:
Unity可以解析Json,但是读取Excel需要插件的帮助,那就把这个功能分离开,读表插件就只管读表转Json,Unity就只管Json解析,中间需要一个存储空间,使用ScriptableObject数据类是个不错的选择。
缺点也很明显,我们只能在Unity编辑器模式下使用这个工具,也就是无法在打包后读取Excel,不过我们再也不用担心读表插件出问题了,因为打包后根本用不到读表插件。
实现步骤:
- 步骤一:Excel数据转换成Json数据
- 步骤二:将数据存储到ScriptableObject持久类中
- 步骤三:Unity读取ScriptableObject类
Excel数据转换成Json数据
因为只考虑在Unity编辑器模式使用,所以直接写一个编辑器窗口就行了
主要功能概述
- 用户界面(EditorWindow):提供一个界面让用户选择 Excel 文件,并进行相应的操作,如选择是否生成 C# 类文件、开始转换等。
- 文件处理:用户可以拖拽文件或文件夹到指定区域,程序会识别路径并加载 Excel 文件列表。
- 转换操作:Excel 文件被读取,转换为 JSON 数据,并保存到指定路径。
- 打开编辑窗口
javascript
[MenuItem("Tools/ExcelToJson")]
public static void ShowWindow()
{
ExcelToUnityWindow window = GetWindow<ExcelToUnityWindow>("Excel 转 Json 工具");
window.minSize = new Vector2(400 , 300);
}
这段代码创建了一个编辑器窗口,当用户点击 Unity 编辑器菜单中的 "Tools/ExcelToJson" 时,会弹出 ExcelToUnityWindow 窗口。
- 初始化并加载 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 数据。
- 文件夹路径选择与拖拽支持
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();
}
}
}
}
这个方法实现了拖拽功能,用户可以将文件或文件夹拖拽到指定区域,程序会检测并更新路径。
- 转换文件的核心操作
javascript
private void ConvertExcelFiles()
{
foreach (string filePath in selectedExcelFiles)
{
ParseFile(filePath , createCS , csOutputPath);
}
EditorUtility.DisplayDialog("完成" , "所有 Excel 文件已成功转换!" , "确定");
}
ConvertExcelFiles() 方法会遍历用户选择的 Excel 文件,并调用 ParseFile() 方法来处理每一个文件。这个方法的核心功能是将 Excel 文件转换为 JSON 格式。
- 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);
}
}
}