Unity desenvolve leitor de tabelas Excel

No desenvolvimento de jogos reais, podemos encontrar a necessidade de ler tabelas do Excel e podemos desenvolver um conjunto de plug-ins que usam leitura e escrita binárias. A ideia geral é dividida em duas etapas:

Passo 1: Leia o arquivo do Excel

1. Gerar as informações da tabela Excel em uma classe de estrutura, para isso precisamos formular uma regra de leitura e escrita para a tabela Excel. Por exemplo a tabela abaixo. A primeira linha é o nome do campo, a segunda linha é o tipo de campo, a terceira linha é a chave primária e a quarta linha é a explicação. Da quinta linha em diante estão os dados que realmente precisam ser persistidos.

2. Após formular as regras da tabela do Excel, passamos a utilizar o código para gerar o container (dicionário) da classe da estrutura de dados e da classe da estrutura de armazenamento. Primeiramente, declaramos um arquivo de classe ToolExcel e três caminhos, conforme segue:

    /// <summary>
    /// excel文件存放的路径
    /// </summary>
    public static string EXCEL_PATH = Application.dataPath + "/ArtRes/Excel/";

    /// <summary>
    /// 数据结构类脚本存储位置路径
    /// </summary>
    public static string DATA_CLASS_PATH = Application.dataPath + "/Scripts/ExcelData/DataClass/";    /// <summary>
    /// 容器类脚本存储位置路径

    /// </summary>
    public static string DATA_CONTAINER_PATH = Application.dataPath + "/Scripts/ExcelData/Container/";

3. Escreva um método que possa ser chamado no editor do Unity. Os recursos devem ser adicionados antes do método, para que o editor do Unity possa ser chamado fora da caixa de seleção. Em seguida, abra o caminho especificado do arquivo de tabela no método, obtenha todos os arquivos nele e use o loop for para julgar se é um arquivo de tabela do Excel.Se não for um arquivo de tabela, vá diretamente para o próximo ciclo. Se for um arquivo de tabela, use o fluxo do arquivo stream para salvar todas as tabelas no arquivo Get it e, em seguida, use foreach para percorrer todas as informações da tabela para executar três operações, o código é o seguinte.

[MenuItem("GameTool/GenerateExcel")]
    private static void GenerateExcelInfo()
    {
        //记在指定路径中的所有Excel文件 用于生成对应的3个文件
        DirectoryInfo dInfo = Directory.CreateDirectory(EXCEL_PATH);
        //得到指定路径中的所有文件信息 相当于就是得到所有的Excel表
        FileInfo[] files = dInfo.GetFiles();
        //数据表容器
        DataTableCollection tableConllection;
        for (int i = 0; i < files.Length; i++)
        {
            //如果不是excel文件就不要处理了
            if (files[i].Extension != ".xlsx" &&
                files[i].Extension != ".xls")
                continue;
            //打开一个Excel文件得到其中的所有表的数据
            using (FileStream fs = files[i].Open(FileMode.Open, FileAccess.Read))
            {
                IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(fs);
                tableConllection = excelReader.AsDataSet().Tables;
                fs.Close();
            }

            //遍历文件中的所有表的信息
            foreach (DataTable table in tableConllection)
            {
                //生成数据结构类
                GenerateExcelDataClass(table);
                //生成容器类
                GenerateExcelContainer(table);
                //生成2进制数据
                GenerateExcelBinary(table);
            }

        }

Três métodos na travessia:

1. Gerar classe de estrutura de dados Método GenerateExcelDataClass

Mas antes de gerarmos a classe de estrutura de dados, precisamos obter algumas informações na tabela e como é a classe de estrutura de dados gerada dinamicamente que precisamos. Em nosso exemplo de arquivo de tabela, um conjunto de regras é decretado. (Os primeiros quatro comportamentos são nome da variável, tipo/identificação da chave primária e explicação) .

public class Sheet1
{
  public int id;
  public string name;
  public float atk;
  public float def;
}

Em seguida, no método GenerateExcelDataClass, precisamos saber o nome do campo de classe dos dados e o tipo de dados para que possamos unir strings . Para isso, dois métodos, GetVariableNameRow e GetVariableTypeRow , podem ser escritos da seguinte forma:

    /// <summary>
    /// 获取变量名所在行
    /// </summary>
    /// <param name="table"></param>
    /// <returns></returns>
    private static DataRow GetVariableNameRow(DataTable table)
    {
        return table.Rows[0];
    }

    /// <summary>
    /// 获取变量类型所在行
    /// </summary>
    /// <param name="table"></param>
    /// <returns></returns>
    private static DataRow GetVariableTypeRow(DataTable table)
    {
        return table.Rows[1];
    }

Com esses dois métodos, você pode chamar GenerateExcelDataClass para obter as informações da tabela, o código é o seguinte. Sua essência é, na verdade, percorrer dois objetos DataRow após obtê-los e, em seguida, usar a junção de strings para gerar classes.

    /// <summary>
    /// 生成Excel表对应的数据结构类
    /// </summary>
    /// <param name="table"></param>
    private static void GenerateExcelDataClass(DataTable table)
    {
        //字段名行
        DataRow rowName = GetVariableNameRow(table);
        //字段类型行
        DataRow rowType = GetVariableTypeRow(table);

        //判断路径是否存在 没有的话 就创建文件夹
        if (!Directory.Exists(DATA_CLASS_PATH))
            Directory.CreateDirectory(DATA_CLASS_PATH);
        //如果我们要生成对应的数据结构类脚本 其实就是通过代码进行字符串拼接 然后存进文件就行了
        string str = "public class " + table.TableName + "\n{\n";

        //变量进行字符串拼接
        for (int i = 0; i < table.Columns.Count; i++)
        {
            str += "    public " + rowType[i].ToString() + " " + rowName[i].ToString() + ";\n";
        }

        str += "}";

        //把拼接好的字符串存到指定文件中去
        File.WriteAllText(DATA_CLASS_PATH + table.TableName + ".cs", str);

        //刷新Project窗口
        AssetDatabase.Refresh();
    }

Além da classe de estrutura de dados, também precisamos de uma classe de contêiner de dados

2. Gerar classe de contêiner de dados Método GenerateExcelContainer

A essência da classe contêiner de dados é, na verdade, uma classe usada para armazenar dados linha por linha, dos quais existem apenas variáveis ​​de dicionário. A chave do dicionário é a chave dos dados na tabela, e o valor do dicionário é a classe da estrutura de dados, que é usada para armazenar uma linha de dados.

public class Sheet1Container
{
    public Dictionary<int,Sheet1>dataDic = new Dictionary<int, Sheet1>();
}

 Para fazer isso, precisamos de um método GetKeyIndex . Como a tabela possui regras estabelecidas (a terceira linha da tabela marca a chave primária), precisamos saber o valor do índice da chave primária para determinar qual coluna da terceira linha é o índice. código mostra como abaixo:

        private static int GetKeyIndex(DataTable table) { 
        DataRow row = table.Rows[2];
        for (int i = 0; i < table.Columns.Count; i++)
        {
            if (row[i].ToString() == "key")
                return i;
        }
        return 0;
    }

Depois de obter a coluna onde está localizada a chave, você pode saber quais dados são usados ​​como chave e substituir os dados que são usados ​​como chave primária na função. O código é o seguinte:

    /// <summary>
    /// 生成Excel表对应的数据容器类
    /// </summary>
    /// <param name="table"></param>
    private static void GenerateExcelCotainer(DataTable table) {
        int keyIndex = GetKeyIndex(table);
        DataRow rowType = GetVariableTypeRow(table);
        if (!Directory.Exists(BinaryDataMgr.DATA_CONTAINER_PATH))
            Directory.CreateDirectory(BinaryDataMgr.DATA_CONTAINER_PATH);
        string str = "using System.Collections.Generic;\n";
        str += "public class " + table.TableName + "Container" + "\n{\n";
        str += "    ";
        str += "public Dictionary<" + rowType[keyIndex].ToString() + "," + table.TableName + ">";
        str += "dataDic = new " + "Dictionary<" + rowType[keyIndex].ToString() + ", " + table.TableName + ">();\n";
        str += "}";
        File.WriteAllText(BinaryDataMgr.DATA_CONTAINER_PATH+"/"+ table.TableName + "Container.cs",str);
        AssetDatabase.Refresh();
    }

Depois de obter as duas classes de dados, você pode começar a gerar arquivos binários

3. Gere um arquivo binário GenerateExcelBinary(DataTable tabel) O parâmetro de entrada é uma tabela

Na verdade, esse método é primeiro julgar se existe um caminho para armazenar arquivos binários, se não, criá-lo e, em seguida, abrir um fluxo de dados de arquivo binário. Escreva as informações de dados da tabela de entrada em sequência.

1. Primeiro escreva quantas linhas de dados precisam ser armazenadas (uma vez que as regras formuladas por nossa tabela do Excel são as primeiras quatro linhas de informações da tabela, então os dados começam na quinta e na quinta linha)

2. Escreva o nome da variável da chave primária (existe um método pronto para obter o índice da chave primária). Você só precisa obter os dados do índice da linha de dados do nome da variável e convertê-lo em um corda

3. Percorra todas as linhas de conteúdo e, em seguida, escreva de acordo com o tipo (a escrita é dados escritos linha por linha. Primeiro percorra todas as linhas de acordo com o número de linhas e, em seguida, julgue o tipo por coluna de acordo com o comprimento do digite linha e escreva o meio do fluxo de arquivo)

4. Por fim, feche o fluxo

    /// <summary>
    /// 生成Excel二进制数据
    /// </summary>
    /// <param name="table"></param>
    private static void GenerateExcelBinary(DataTable table){
        if (Directory.Exists(BinaryDataMgr.DATA_CONTAINER_PATH))
            Directory.CreateDirectory(BinaryDataMgr.DATA_CONTAINER_PATH);
        //创建一个二进制文件写入
        using (FileStream fs = new FileStream(BinaryDataMgr.DATA_CONTAINER_PATH + table.TableName + ".mqx", FileMode.OpenOrCreate, FileAccess.Write
            )) {
            //存储具体的excel对应的二进制信息
            //1.先存储我们需要写入多少行的数据 方便读取
            fs.Write(BitConverter.GetBytes(table.Rows.Count - 4),0,4);
            //2.存储主键的变量名
            string keyName = GetVariableNameRow(table)[GetKeyIndex(table)].ToString();
            byte[] bytes = Encoding.UTF8.GetBytes(keyName);
            //存储字符串字节数组长度
            fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);
            //存储字符串字节数组
            fs.Write(bytes, 0, bytes.Length);
            //3.遍历所有内容的行 进行二进制的写入
            DataRow row;
            //得到类型行 根据规则决定如何写入书
            DataRow rowType = GetVariableTypeRow(table);
            for (int i = BINGEN_INDEX; i < table.Rows.Count; i++)
            {
                row = table.Rows[i];
                for (int j = 0; j < table.Columns.Count; j++)
                {
                    switch (rowType[j].ToString())
                    {
                        case "int":
                            fs.Write(BitConverter.GetBytes(int.Parse(row[j].ToString())),0,4);
                            break;
                        case "float":
                            fs.Write(BitConverter.GetBytes(int.Parse(row[j].ToString())), 0,4);
                            break;
                        case "sting":
                            fs.Write(BitConverter.GetBytes(int.Parse(row[j].ToString())), 0, 1);
                            break;
                        case "bool":
                            bytes = Encoding.UTF8.GetBytes(row[j].ToString());
                            //写入字符串字节数组长度
                            fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);
                            //写入字符串字节数组
                            fs.Write(bytes, 0, bytes.Length);
                            break;
                    }
                }
            }
            fs.Close();
        }
        AssetDatabase.Refresh();
    }

Do exposto, concluímos a leitura e gravação de arquivos binários para tabelas do Excel.

Mas depois de salvar a tabela do Excel como um sistema secundário, precisamos lê-la e armazená-la na memória. Por esta razão, também precisamos de uma classe BinaryDataManager para ler arquivos binários.Como é uma classe de gerenciador Manager, ela pode ser escrita como um modo singleton. Para isso, podemos primeiro escrever um padrão singleton. código mostra como abaixo:

Uma interface singleton, uma abstração singleton para inicialização. Então, há três campos necessários na classe, um dos quais é DATA_BINARY_PATH (o caminho de armazenamento de dados binários) que será usado pelo ToolExcel. Há também um contêiner de dicionário para armazenamento obtido do disco para a memória.

public interface ISingleton {
    void Init();
}

public abstract class Singleton<T> where T : ISingleton, new() {
    private static T instance;
    public static T Instance {
        get {
            if (instance==null)
            {
                instance = new Lazy<T>(true).Value;
                instance.Init();
            }
            return instance;
        }
    }
}

/// <summary>
/// 2进制数据管理器
/// </summary>
public class BinaryDataMgr:ISingleton
{
/// <summary>
    /// 2进制数据存储位置路径
    /// </summary>
    public static string DATA_BINARY_PATH = Application.streamingAssetsPath + "/Binary/";

    /// <summary>
    /// 用于存储所有Excel表数据的容器
    /// </summary>
    private Dictionary<string, object> tableDic = new Dictionary<string, object>();

    /// <summary>
    /// 数据存储的位置
    /// </summary>
    private static string SAVE_PATH = Application.persistentDataPath + "/Data/";
    public static BinaryDataMgr Instace => Singleton<BinaryDataMgr>.Instance;
}

Há também um método LoadTable<T,K>. Os dois parâmetros genéricos são o nome da classe do contêiner e o nome da classe da estrutura de dados, respectivamente. O código do método é o seguinte:

Podemos dividir este método em várias partes

1. Abra o stream para ler o arquivo, armazene o bytecode no stream e então feche o stream

2. Leia as informações de características armazenadas no fluxo, como quantas linhas devem ser lidas e o nome da chave primária

3. A reflexão cria objetos de classe contêiner

4. Obtenha o tipo da classe de estrutura de dados e reflita para obter todas as informações nela

5. Leia cada linha de informação, instancie um objeto de estrutura de dados por meio de reflexão, a seguir percorra o objeto de informação de estrutura de dados (infos) para determinar o tipo de dados nele e leia o tipo correspondente da matriz de bytes

Como a ordem na qual os arquivos binários são gravados é escrita de acordo com o tipo da classe da estrutura de dados, não haverá problema em ler linha por linha na ordem.

6. Obtenha o objeto de dicionário no objeto de contêiner, use o método Add do dicionário para refletir a barra, obtenha o valor da chave, use o método de chamada de reflexão e, finalmente, registre a tabela de leitura.

7. Feche o fluxo

    public void LoadTable<T,K>()
    {
        //读取 excel表对应的2进制文件 来进行解析
        using (FileStream fs = File.Open(DATA_BINARY_PATH + typeof(K).Name + ".tang", FileMode.Open, FileAccess.Read))
        {
            byte[] bytes = new byte[fs.Length];
            fs.Read(bytes, 0, bytes.Length);
            fs.Close();
            //用于记录当前读取了多少字节了
            int index = 0;

            //读取多少行数据
            int count = BitConverter.ToInt32(bytes, index);
            index += 4;

            //读取主键的名字
            int keyNameLength = BitConverter.ToInt32(bytes, index);
            index += 4;
            string keyName = Encoding.UTF8.GetString(bytes, index, keyNameLength);
            index += keyNameLength;

            //创建容器类对象
            Type contaninerType = typeof(T);
            object contaninerObj = Activator.CreateInstance(contaninerType);
            //得到数据结构类的Type
            Type classType = typeof(K);
            //通过反射 得到数据结构类 所有字段的信息
            FieldInfo[] infos = classType.GetFields();

            //读取每一行的信息
            for (int i = 0; i < count; i++)
            {
                //实例化一个数据结构类 对象
                object dataObj = Activator.CreateInstance(classType);
                foreach (FieldInfo info in infos)
                {
                    if( info.FieldType == typeof(int) )
                    {
                        //相当于就是把2进制数据转为int 然后赋值给了对应的字段
                        info.SetValue(dataObj, BitConverter.ToInt32(bytes, index));
                        index += 4;
                    }
                    else if (info.FieldType == typeof(float))
                    {
                        info.SetValue(dataObj, BitConverter.ToSingle(bytes, index));
                        index += 4;
                    }
                    else if (info.FieldType == typeof(bool))
                    {
                        info.SetValue(dataObj, BitConverter.ToBoolean(bytes, index));
                        index += 1;
                    }
                    else if (info.FieldType == typeof(string))
                    {
                        //读取字符串字节数组的长度
                        int length = BitConverter.ToInt32(bytes, index);
                        index += 4;
                        info.SetValue(dataObj, Encoding.UTF8.GetString(bytes, index, length));
                        index += length;
                    }
                }

                //读取完一行的数据了 应该把这个数据添加到容器对象中
                //得到容器对象中的 字典对象
                object dicObject = contaninerType.GetField("dataDic").GetValue(contaninerObj);
                //通过字典对象得到其中的 Add方法
                MethodInfo mInfo = dicObject.GetType().GetMethod("Add");
                //得到数据结构类对象中 指定主键字段的值
                object keyValue = classType.GetField(keyName).GetValue(dataObj);
                mInfo.Invoke(dicObject, new object[] { keyValue, dataObj });
            }

            //把读取完的表记录下来
            tableDic.Add(typeof(T).Name, contaninerObj);
            fs.Close();
        }
    }

Declare uma função que chame este método externamente, passe a estrutura da classe de dados gerada e o nome do tipo do contêiner de dados como um método genérico para a função acima e, em seguida, os dados binários armazenados no disco rígido podem ser obtidos na memória.

Concluída a implementação do editor.

Acho que você gosta

Origin blog.csdn.net/qq_52690206/article/details/127843732
Recomendado
Clasificación