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(',');
        }
    }
}

猜你喜欢

转载自blog.csdn.net/YasinXin/article/details/105891411