我之前寫過一篇Excel轉Asset的文章,鏈接:https://blog.csdn.net/YasinXin/article/details/102524921
但當項目的Excel特別多時,那種方法還是不夠靈活方便。
之前的是要根據接受的Excel寫好類和讀取方法,這次是用一個統一的方法讀取不同的Excel文件,且自動生成相應的類。
那麼就開始吧。
一、Excel格式
既然是做成統一自動讀取,那個Excel也需要一個統一的規範。
示例:
第一行爲字段屬性名,第二行爲字段類型,從第三行開始填入數據
最後把文件保存爲csv的文件格式,主要方便讀取。
注意點:
1.文件名要和表格內容相關的名字,後面生成代碼會用到。
2.csv文件要使用UTF-8的編碼格式,特別是表格中有中文。
二、編輯器擴展
1.在Editor文件夾創建一個編輯器擴展代碼
2.創建一個工具界面
[MenuItem("Tool/Excel解析窗口")]
static void ByWindow()
{
CreatConfigData window = EditorWindow.GetWindow<CreatConfigData>();
}
3. 繪製面板的功能界面,並聲明要使用的字段
/// <summary>
/// 代碼寫入路徑
/// </summary>
static string writePath = "/Editor/GameConfigs/";
/// <summary>
/// asset保存路徑
/// </summary>
static string assetPath = "Assets/Resources/Config/";
/// <summary>
/// 編輯器中選中的對象
/// </summary>
static UnityEngine.Object selectObj;
/// <summary>
/// 存放表格數據的數組
/// </summary>
static string[][] array = null;
創建好對應路徑保存的文件夾
繪製面板
private void OnGUI()
{
string csvPath = string.Empty; //用來保存csv文件路徑
GUILayout.Label("設置配置數據文件的生成路徑");
writePath = GUILayout.TextField(writePath);
if (GUILayout.Button("請選擇一個合法的CSV文件"))
{
csvPath = EditorUtility.OpenFilePanel("Overwrite with csv", "", "csv");
if (csvPath.Length != 0)
{
Debug.Log(csvPath);
if (!csvPath.ToLower().EndsWith(".csv"))
{
Debug.LogWarning("請選擇csv文件");
return;
}
}
}
GUILayout.Label("請中生成asset的.cs文件:");
if (Selection.activeObject != null)
{
string path = AssetDatabase.GetAssetPath(Selection.activeObject);
if (Path.GetExtension(path.ToLower()).Equals(".cs"))
{
selectObj = Selection.activeObject;
GUILayout.Label(path);
}
}
if (GUILayout.Button("生成asset文件"))
{
}
}
這樣編輯器工具面板就畫好了,效果如下:
三、功能實現
思路:先讀取表格的字段屬性,生成腳本代碼,再根據生成的腳本代碼還有表格數據生成asset文件。
1.根據表格字段屬性生成腳本
需要的腳本範例,如下
我們需要一個表格字段的類,還需一個用List存儲表格字段類的類,並且這個類需要有一個添加的方法。
public partial class OnPiceDatas : ScriptableObject
{
public List<OnPiceData> listOnPiceData= new List<OnPiceData>();
public void AddList (OnPiceData data)
{
listOnPiceData.Add(data);
}
}
[System.Serializable]
public partial class OnPiceData
{
public int id;
public string name;
public int age;
public string sex;
}
根據需求寫一個腳本生成方法
傳入的 filePath爲csv的路徑,writePath爲腳本保存路徑
public static string[][] CreatConfigFile(string filePath, string writePath)
{
string[][] array = null;
Debug.Log(filePath + " " + filePath.LastIndexOf("/"));
string className = filePath.Substring(filePath.LastIndexOf("/") + 1).Replace(".csv", "").Replace(".CSV", "");
Debug.Log(className);
StreamWriter sw = new StreamWriter(Application.dataPath + writePath + className + "s.cs");
sw.WriteLine("using UnityEngine;\nusing System.Collections;\nusing System.Collections.Generic;\n");
sw.WriteLine("public partial class " + className + "s : ScriptableObject");
sw.WriteLine("{");
sw.WriteLine("\tpublic List<" + className + "> list" + className + "= new List<" + className + ">();");
sw.WriteLine("\t" + "public void AddList (" + className + " data)");
sw.WriteLine("\t" + "{");
sw.WriteLine("\t\t" + "list" + className + ".Add(data);");
sw.WriteLine("\t" + "}");
sw.WriteLine("}");
sw.WriteLine("\n[System.Serializable]");
sw.WriteLine("public partial class " + className);
sw.WriteLine("{");
array = ReadCsvData(filePath);
for (int j = 0; j < array[0].Length; j++)
{
string fieldName = array[0][j];
string fieldType = array[1][j];
Debug.Log("public " + fieldType + " " + fieldName);
sw.WriteLine("\t" + "public " + fieldType + " " + fieldName + ";");
}
sw.WriteLine("}");
sw.Flush();
sw.Close();
AssetDatabase.Refresh();
Debug.Log("save script");
return array;
}
操作如下就生成了我們需要的腳本代碼
2.生成asset文件
這一步算是個難點,因爲不是單純的一個asset文件,還得把數據填入進去,而類和字段都是不確定的。
這裏就需要用到反射的機制,以前我對反射了解的也不是深,所以費了很大功夫來研究這塊。
先上代碼:
for (int m = 2; m < array.Length - 1; m++)
{
Type classType = Type.GetType(className);
object classObj = Activator.CreateInstance(classType);
FieldInfo[] fis = classType.GetFields();
for (int i = 0; i < fis.Length; i++)
{
Debug.Log(fis[i]);
string[] strs = fis[i].ToString().Split(' ');
Debug.Log("strs[0]====>" + strs[0] + array[m][i]);
switch (strs[0])
{
case "System.Int32":
fis[i].SetValue(classObj, int.Parse(array[m][i]));
break;
case "System.Int64":
fis[i].SetValue(classObj, long.Parse(array[m][i]));
break;
case "System.String":
fis[i].SetValue(classObj, array[m][i]);
break;
default:
break;
}
}
MethodInfo methodInfo = dataType.GetMethod("AddList");
object[] parameters = new object[] { classObj };
methodInfo.Invoke(dataObj, parameters);
}
第一個循環從2開始是因爲,表格中除掉字段屬性後數據是從第三行開始的。
Type classType = Type.GetType(className); 這裏className是表格名字,通過名字得到一個Type對象。
注意:生成的腳本一定也要放到Editor文件下才能被獲取到Type(原因大概是Editor目錄和其他目錄不在同一個程序集)
classType.GetFields(); 可以獲取到類中的所有Public字段,再通過SetValue()方法給字段賦值。
再包含List的類中通過GetMethod()方法獲取到我們之前說的那個添加List的方法,然後用Invoke實現調用。
在生腳本文件時我們把表格數據保存在了array字段裏,但有時Unity重新編譯腳本是緩存會被清除,所以需要再調用一下表格。
if (array == null)
{
csvPath = EditorUtility.OpenFilePanel("Overwrite with csv", "", "csv");
if (csvPath.Length != 0)
{
if (!csvPath.ToLower().EndsWith(".csv"))
{
Debug.LogWarning("請選擇csv文件");
return;
}
ReadCsvData(csvPath);
}
}
操作時我們要先選中腳本,然後會根據腳本生成對應的asset文件,如下
最後生成asset文件如下
這樣就完成了,我們可以再換個表格試試
同樣的操作後
四、完整代碼
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System;
using System.Reflection;
/// <summary>
/// Copyright (C) 2020 YasinXin
///
/// 作者: #AuthorName#
/// 創建日期: #CreateTime#
/// 文件名: CreatConfigData.cs
/// 描述:
/// </summary>
public class CreatConfigData : EditorWindow
{
/// <summary>
/// 代碼寫入路徑
/// </summary>
static string writePath = "/Editor/GameConfigs/";
/// <summary>
/// asset保存路徑
/// </summary>
static string assetPath = "Assets/Resources/Config/";
/// <summary>
/// 編輯器中選中的對象
/// </summary>
static UnityEngine.Object selectObj;
/// <summary>
/// 存放表格數據的數組
/// </summary>
static string[][] array = null;
[MenuItem("Tool/Excel解析窗口")]
static void ByWindow()
{
CreatConfigData window = EditorWindow.GetWindow<CreatConfigData>();
}
private void OnGUI()
{
string csvPath = string.Empty;
GUILayout.Label("設置配置數據文件的生成路徑");
writePath = GUILayout.TextField(writePath);
if (GUILayout.Button("請選擇一個合法的CSV文件"))
{
csvPath = EditorUtility.OpenFilePanel("Overwrite with csv", "", "csv");
if (csvPath.Length != 0)
{
Debug.Log(csvPath);
if (!csvPath.ToLower().EndsWith(".csv"))
{
Debug.LogWarning("請選擇csv文件");
return;
}
CreatConfigFile(csvPath, writePath);
}
}
GUILayout.Label("請中生成asset的.cs文件:");
if (Selection.activeObject != null)
{
string path = AssetDatabase.GetAssetPath(Selection.activeObject);
if (Path.GetExtension(path.ToLower()).Equals(".cs"))
{
selectObj = Selection.activeObject;
GUILayout.Label(path);
}
}
if (GUILayout.Button("生成asset文件"))
{
if (selectObj != null)
{
string dataName = selectObj.name;
string className = dataName.Substring(0, dataName.Length - 1);
Type dataType = Type.GetType(dataName);
object dataObj = Activator.CreateInstance(dataType);
if (array == null)
{
csvPath = EditorUtility.OpenFilePanel("Overwrite with csv", "", "csv");
if (csvPath.Length != 0)
{
if (!csvPath.ToLower().EndsWith(".csv"))
{
Debug.LogWarning("請選擇csv文件");
return;
}
ReadCsvData(csvPath);
}
}
Debug.Log(array.Length);
for (int m = 2; m < array.Length - 1; m++)
{
Type classType = Type.GetType(className);
object classObj = Activator.CreateInstance(classType);
FieldInfo[] fis = classType.GetFields();
for (int i = 0; i < fis.Length; i++)
{
Debug.Log(fis[i]);
string[] strs = fis[i].ToString().Split(' ');
Debug.Log("strs[0]====>" + strs[0] + array[m][i]);
switch (strs[0])
{
case "System.Int32":
fis[i].SetValue(classObj, int.Parse(array[m][i]));
break;
case "System.Int64":
fis[i].SetValue(classObj, long.Parse(array[m][i]));
break;
case "System.String":
fis[i].SetValue(classObj, array[m][i]);
break;
default:
break;
}
}
MethodInfo methodInfo = dataType.GetMethod("AddList");
object[] parameters = new object[] { classObj };
methodInfo.Invoke(dataObj, parameters);
}
AssetDatabase.CreateAsset((UnityEngine.Object)dataObj, assetPath + dataName + ".asset");
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("ConfigCreater", string.Format("生成成功:", dataObj), "OK");
}
}
}
private void OnSelectionChange() //當編輯器選中對象改變時調用
{
Repaint(); //重新繪製界面
}
/// <summary>
/// 更具表格中的屬性生成腳本
/// </summary>
/// <param name="filePath"></param>
/// <param name="writePath"></param>
/// <returns></returns>
public static string[][] CreatConfigFile(string filePath, string writePath)
{
Debug.Log(filePath + " " + filePath.LastIndexOf("/"));
string className = filePath.Substring(filePath.LastIndexOf("/") + 1).Replace(".csv", "").Replace(".CSV", "");
Debug.Log(className);
StreamWriter sw = new StreamWriter(Application.dataPath + writePath + className + "s.cs");
sw.WriteLine("using UnityEngine;\nusing System.Collections;\nusing System.Collections.Generic;\n");
sw.WriteLine("public partial class " + className + "s : ScriptableObject");
sw.WriteLine("{");
sw.WriteLine("\tpublic List<" + className + "> list" + className + "= new List<" + className + ">();");
sw.WriteLine("\t" + "public void AddList (" + className + " data)");
sw.WriteLine("\t" + "{");
sw.WriteLine("\t\t" + "list" + className + ".Add(data);");
sw.WriteLine("\t" + "}");
sw.WriteLine("}");
sw.WriteLine("\n[System.Serializable]");
sw.WriteLine("public partial class " + className);
sw.WriteLine("{");
ReadCsvData(filePath);
for (int j = 0; j < array[0].Length; j++)
{
string fieldName = array[0][j];
string fieldType = array[1][j];
Debug.Log("public " + fieldType + " " + fieldName);
sw.WriteLine("\t" + "public " + fieldType + " " + fieldName + ";");
}
sw.WriteLine("}");
sw.Flush();
sw.Close();
AssetDatabase.Refresh();
Debug.Log("save script");
return array;
}
/// <summary>
/// 讀取csv中的數據
/// </summary>
public static void ReadCsvData(string path)
{
string str = File.ReadAllText(path);
//讀取每一行的內容
string[] lineArray = str.Split("\r"[0]);
//創建二維數組
array = new string[lineArray.Length][];
//把csv中的數據儲存在二位數組中
for (int i = 0; i < lineArray.Length; i++)
{
array[i] = lineArray[i].Split(',');
}
}
}