Unity中利用反射自動讀取Excel配置

我之前寫過一篇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(',');
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章