使用 Unity 将 XLSX 文件转换为 CSV 或 Json 文件
1.前言
在游戏开发和数据处理过程中,CSV(Comma-Separated Values)或者 Json 文件经常被用作配置文件或数据传输的中间格式。而许多数据往往最初存储在 Excel 的 XLSX 文件中。因此,将这些 XLSX 文件转换为 CSV 文件成为了一个常见需求。
2.Xlsx转成Csv
本文将介绍如何使用 Unity 和 NPOI 库来实现一个 XLSX 转 CSV 的工具,以及如何在 Unity 中解析 CSV 文件。
使用 NPOI 将 XLSX 文件转换为 CSV
首先,我们使用了 NPOI 库,它能够兼容 WPS 和不同版本的 Excel 文件。为了简化操作,我们通过 Unity 的 Editor 窗口创建了一个工具类 XlsxToCsv,它允许用户选择输入和输出路径,并定义 CSV 文件的行数限制。
关于NOPI库,已经放在Git的目录下
1.文件路径的读取与保存
目前用OnGui实现一个简单的显示界面
string inputPath = "";
string outputPath = "";
int lineCount = 0;
[MenuItem("XlsxTools/通用Xlsx to Csv")]
public static void ShowWindow()
{
EditorWindow.GetWindow(typeof(XlsxToCsv));
}
Dictionary<string, string> csvFileOuts = new Dictionary<string, string>();
//生成显示界面
private void OnGUI()
{
GUILayout.Label("Base Settings", EditorStyles.boldLabel);
if (GUILayout.Button("Select Input Path"))
{
string defaultPath = string.IsNullOrEmpty(inputPath) ? "" : inputPath;
inputPath = EditorUtility.OpenFolderPanel("Select Input Folder", defaultPath, "");
}
EditorGUILayout.LabelField("Input Path: " + inputPath);
if (GUILayout.Button("Select Output Path"))
{
string defaultPath = string.IsNullOrEmpty(outputPath) ? "" : outputPath;
outputPath = EditorUtility.OpenFolderPanel("Select Output Folder", defaultPath, "");
}
EditorGUILayout.LabelField("Output Path: " + outputPath);
lineCount = EditorGUILayout.IntField("Line Count: ", lineCount);
if (GUILayout.Button("Convert"))
{
//进行数据存储
EditorPrefs.SetString("inputPathXTC", inputPath);
EditorPrefs.SetString("outputPathXTC", outputPath);
EditorPrefs.SetInt("lineCountXTC", lineCount);
string projectPath = Application.dataPath.Replace('/', '\\');
ConvertFiles(inputPath, outputPath);
}
}
private void OnEnable()
{
inputPath = EditorPrefs.GetString("inputPathXTC", "");
outputPath = EditorPrefs.GetString("outputPathXTC", "");
lineCount = EditorPrefs.GetInt("lineCountXTC", 0);
}
有了基本显示界面后要进行路径的匹配,使输出的子文件夹路径与输入的路径完全一致
private void ConvertFiles(string inputPath, string outputPath)
{
string[] fileEntries = Directory.GetFiles(inputPath, "*.xlsx", SearchOption.AllDirectories);
foreach (string fileName in fileEntries)
{
string subPath = Path.GetRelativePath(inputPath, fileName);
string outputSubFolder = Path.GetDirectoryName(subPath);
string outputFolderFullPath = Path.Combine(outputPath, outputSubFolder);
Directory.CreateDirectory(outputFolderFullPath);
string outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(subPath);
string outputCsvFile = Path.Combine(outputFolderFullPath, outputFileNameWithoutExtension + ".csv");
if (File.Exists(outputCsvFile))
{
File.Delete(outputCsvFile);
}
if (!csvFileOuts.ContainsKey(fileName))
{
csvFileOuts.Add(fileName, outputCsvFile);
}
}
EachXlsxToCsv();
}
2.将XLSX进行拷贝并生成CSV
在 ConvertXlsxToCsvAc 方法中,实现了对 XLSX 文件的解析,并将其转换为 CSV 格式。这个方法通过 NPOI 读取 Excel 文件内容,并根据指定的行数限制将数据分割为多个 CSV 文件。为了处理不同的 Excel 版本,代码中使用了 XSSFWorkbook 和 HSSFWorkbook 两种解析方式。
private void ConvertXlsxToCsvAc(string inputFileName, string outputFileName, int lineCount = 3)
{
if (string.IsNullOrEmpty(inputFileName) || string.IsNullOrEmpty(outputFileName))
{
Debug.LogError("File paths cannot be null or empty.");
return;
}
if (Path.GetFileName(inputFileName).StartsWith("~$"))
{
Debug.LogWarning("Skipping temporary file: " + inputFileName);
return;
}
//即便表格在打开的状态下,也可进行转表
string tempFilePath = Path.GetTempFileName();
File.Copy(inputFileName, tempFilePath, true);
using (FileStream file = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read))
{
IWorkbook workbook = null;
try
{
workbook = new XSSFWorkbook(file);
}
catch (Exception)
{
//Excel2003 的表格,采用此格式读取
workbook = new HSSFWorkbook(file);
}
ISheet sheet = workbook.GetSheetAt(0);
int maxCellNum = 0;
for (int i = sheet.FirstRowNum; i <= sheet.LastRowNum; i++)
{
IRow row = sheet.GetRow(i);
if (row != null && row.LastCellNum > maxCellNum)
{
maxCellNum = row.LastCellNum;
}
}
// 具体格式在Git地址中有完整实例
}
File.Delete(tempFilePath);
}
3.在 Unity 运行时解析 CSV 文件
除了将 XLSX 文件转换为 CSV,我们还实现了一个方法 ParseCsvConfig,用于在 Unity 中解析生成的 CSV 文件。该方法支持不同数据类型的映射,能够将 CSV 文件中的数据直接转换为游戏中使用的配置对象。
public List<T> ParseCsvConfig<T>(string[] csvContent, char delimiter = '\t') where T : new()
{
var result = new List<T>();
var properties = typeof(T).GetProperties();
for (int i = 3; i < csvContent.Length; i++) // 从第四行开始解析
{
var values = csvContent[i].Split(delimiter); // 根据你的数据格式分割,可能是逗号或其他字符
var obj = new T();
for (int j = 0; j < properties.Length; j++)
{
if (j < values.Length)
{
var property = properties[j];
var value = values[j];
try
{
if (property.PropertyType == typeof(int))
{
property.SetValue(obj, int.Parse(value, CultureInfo.InvariantCulture));
}
else if (property.PropertyType == typeof(float))
{
property.SetValue(obj, float.Parse(value, CultureInfo.InvariantCulture));
}
else if (property.PropertyType == typeof(bool))
{
property.SetValue(obj, bool.Parse(value));
}
else if (property.PropertyType == typeof(string))
{
property.SetValue(obj, value);
}
else if (property.PropertyType == typeof(AchievementReward))
{
property.SetValue(obj, new AchievementReward(value));
}
else if (property.PropertyType == typeof(int[]))
{
var intArray = value.Split('|').Select(int.Parse).ToArray();
property.SetValue(obj, intArray);
}
else if (property.PropertyType == typeof(List<int>))
{
var intList = value.Split('|').Select(int.Parse).ToList();
property.SetValue(obj, intList);
}
else if (property.PropertyType == typeof(List<float>))
{
var intList = value.Split('|').Select(float.Parse).ToList();
property.SetValue(obj, intList);
}
// 可以根据需要添加更多类型转换
}
catch (Exception ex)
{
Console.WriteLine($"Failed to set property {property.Name} with value {value}: {ex.Message}");
}
}
}
result.Add(obj);
}
return result;
}
4.获取到CSV文件数据
使用的话也很简单,去读到让在本地的CSV文件,然后配置需要接收的类,就可以正确读取到
可以用此方法读取本地的文件
public string ReadDataByResources(string _path)
{
TextAsset textAsset = Resources.Load<TextAsset>(_path);
string content = string.Empty;
if (textAsset)
{
content = Encoding.UTF8.GetString(textAsset.bytes);
}
else
{
Debug.LogError("not found json file");
}
if (content.Equals(string.Empty))
{
Debug.LogWarning("file not exit or empty");
}
content = content.TrimStart('\uFEFF');
return content;
}
进行调用
public List<LevelData> GetLevelItems()
{
string csvContent = ReadDataByResources("Csv/levels");
var _configItems = ParseCsvConfigFromContent<LevelData>(csvContent, ',');
return _configItems;
}
3.Xlsx转成Json
将转表Json工具放在了Tool目录下,在unity中调用,输入跟输出路径,即可转出json
@echo off
chcp 65001
@SET EXCEL_FOLDER=%1 //xlsx路径
@SET JSON_FOLDER=%2 //json输出路径
@SET EXE_PATH=%3 //exe相对路径
@ECHO Converting excel files in folder %EXCEL_FOLDER% ...
for /f "delims=" %%i in ('dir /b /a-d /s %EXCEL_FOLDER%\*.xlsx') do (
@echo processing %%~nxi
@CALL "%EXE_PATH%" --excel "%%i" --json "%JSON_FOLDER%\%%~ni.json" --header 3 --exclude_prefix ! --cell_json true --array true
)
具体Unity中的实现
public void RunBatFile()//(string batFilePath, string inAndOutPath)//string excelFilePath, string outputFolder)
{
if (jsonFlors.Count <= 0)
{
return;
}
Process process = new Process();
process.StartInfo.FileName = batFilePath;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true; // Redirect the standard error stream
string inAndOutPath = jsonFlors[jsonFlors.Count - 1];
process.StartInfo.Arguments = inAndOutPath;//"\"" + excelFilePath + "\" \"" + outputFolder + "\""; // pass the excel file path and output folder as arguments
process.Start();
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
if (!string.IsNullOrEmpty(output))
{
UnityEngine.Debug.Log(output);
}
if (!string.IsNullOrEmpty(error))
{
UnityEngine.Debug.LogError(error);
}
jsonFlors.RemoveAt(jsonFlors.Count - 1);
RunBatFile();
}
其余部分包括Ongui显示等,与转CSV基本一致
4.总结
通过本文介绍的方法,您可以在 Unity 中就可实现 XLSX 到 CSV 文件的转换,并解析这些 CSV 文件用于游戏配置。无论是在开发工具链中使用,还是在运行时加载数据,这些功能都将提高开发的效率。希望本文对您在 Unity 中处理 Excel 数据有所帮助。