【Unity】Excel配置工具

1、功能介绍

通过Excel表配置表数据,一键生成对应Excel配置表的数据结构类、数据容器类、已经二进制数据文件,加载二进制数据文件获取所有表数据

需要使用Excel读取的dll包

2、关键代码

2.1 ExcelTool类

实现一键生成Excel配置表的数据结构类、数据容器类、已经二进制数据文件

cs 复制代码
using Excel;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;

public class ExcelTool
{
    /// <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/";

    /// <summary>
    /// 二进制数据存储路径
    /// </summary>
    //public static string DATA_BINARY_PATH = Application.streamingAssetsPath + "/Bianry/";

    /// <summary>
    /// 真正内容开始行号
    /// </summary>
    public static int BEGIN_INDEX = 4;

    [MenuItem("GameTool/GenerateExcelInfo")]
    private static void GenerateExcelInfo()
    {
        //加载指定路径中的所有Excel文件 用于生成对应的3个文件
        DirectoryInfo dInfo = Directory.CreateDirectory(EXCEL_PATH);
        //得到指定路径中的所有文件信息 相当于就是得到所有的Exce1表
        FileInfo[] files = dInfo.GetFiles();
        //数据容器
        DataTableCollection tableCollection;
        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);
                tableCollection = excelReader.AsDataSet().Tables;
                fs.Close();
            }
            //遍历文件中的所有表的信息
            foreach (DataTable table in tableCollection)
            {
                Debug.Log(table.TableName);
                //生成数据结构类
                GenerateExcelDataClass(table);
                //生成容器类
                GenerateExcelContainer(table);
                //生成2进制数据
                GenerateExcelBinary(table);
            }
        }
    }

    /// <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 += "\tpublic " + rowType[i].ToString() + " " + rowName[i].ToString() + ";\n";
        }

        str += "}\n";

        //把拼接好的字符串存进占地文件中
        File.WriteAllText(DATA_CLASS_PATH + table.TableName + ".cs", str);

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

    /// <summary>
    /// 生成Excel表对应的数据容器类
    /// </summary>
    /// <param name="table"></param>
    private static void GenerateExcelContainer(DataTable table)
    {
        //得到主键索引
        int keyIndex = GetKeyIndex(table);
        //得到字段类型行
        DataRow rowType = GetVariableTypeRow(table);
        //没有路径就创建路径
        if (!Directory.Exists(DATA_CONTAINER_PATH)) 
            Directory.CreateDirectory (DATA_CONTAINER_PATH);

        string str = "using System.Collections.Generic;\n\n";

        str += "public class " + table.TableName + "Container" + "\n{\n";

        str += "\tpublic Dictionary<" + rowType[keyIndex].ToString() + ", " + table.TableName + ">";
        str += "dataDic = new Dictionary<" + rowType[keyIndex].ToString() + ", " + table.TableName + ">();\n";

        str += "}";

        File.WriteAllText(DATA_CONTAINER_PATH + table.TableName + "Container.cs", str);

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

    }

    /// <summary>
    /// 生成Excel 2进制数据
    /// </summary>
    /// <param name="table"></param>
    private static void GenerateExcelBinary(DataTable table)
    {
        //没有路径创建路径
        if(!Directory.Exists(BinaryDataMgr.DATA_BINARY_PATH)) 
            Directory.CreateDirectory(BinaryDataMgr.DATA_BINARY_PATH);

        //创建一个2进制文件进行写入
        using(FileStream fs = new FileStream(BinaryDataMgr.DATA_BINARY_PATH + table.TableName + ".zhou", FileMode.OpenOrCreate, FileAccess.Write))
        {
            //存储具体的Excel对应的2进制信息
            //1.先要存储需要写的行数
            //-4 因为前面4行是配置规则 不是需要记录的数据内容
            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(keyName.Length), 0, 4);
            //存储字符串字节数组
            fs.Write(bytes, 0, bytes.Length);

            //遍历所有内容的行 进行2进制的写入
            DataRow row;
            //得到类型行 根据类型来决定应该如何写入
            DataRow rowType = GetVariableTypeRow(table);
            for (int i = BEGIN_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(float.Parse(row[j].ToString())), 0, 4);
                            break;
                        case "bool":
                            fs.Write(BitConverter.GetBytes(bool.Parse(row[j].ToString())), 0, 1);
                            break;
                        case "string":
                            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();
    }

    /// <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];
    }

    /// <summary>
    /// 获取主键索引
    /// </summary>
    /// <param name="table"></param>
    /// <returns></returns>
    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;
    }

}

2.2 BinaryDataMgr

保存数据,读取数据

读取Excel表生成的二进制文件,加载到内容中以供使用

cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using UnityEngine;

/// <summary>
/// 2进制数据管理器
/// </summary>
public class BinaryDataMgr
{
    private static BinaryDataMgr instance = new BinaryDataMgr();
    public static BinaryDataMgr Instance => instance;
    private BinaryDataMgr() { }

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

    /// <summary>
    /// 数据存储路径
    /// </summary>
    private static string SAVE_PATH = Application.persistentDataPath + "/Data/";
    /// <summary>
    /// 二进制数据存储路径
    /// </summary>
    public static string DATA_BINARY_PATH = Application.streamingAssetsPath + "/Bianry/";

    public void InitData()
    {
        //加载自己的表到内存
        //LoadTable<TowerInfoContainer, TowerInfo>();
        //LoadTable<PlayerInfo1Container, PlayerInfo1>();
        //LoadTable<TestInfoContainer, TestInfo>();
    }

    /// <summary>
    /// 加载Excel表的2进制数据到内存中
    /// </summary>
    /// <typeparam name="T">容器类名</typeparam>
    /// <typeparam name="K">数据结构体类名</typeparam>
    public void LoadTable<T, K>()
    {
        //读取Excel表对应的2进制文件  来进行解析
        using (FileStream fs = File.Open(DATA_BINARY_PATH + typeof(K).Name + ".zhou", 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 dicObj = contaninerType.GetField("dataDic").GetValue(contaninerObj);

                //得到容器字段对象
                MethodInfo mInfo = dicObj.GetType().GetMethod("Add");
                //得到数据结构类对象中 指定主键字段的值
                object keyValue = classType.GetField(keyName).GetValue(dataObj);
                mInfo.Invoke(dicObj, new object[] { keyValue, dataObj });
            }

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

        }
    }

    /// <summary>
    /// 得到一张表的信息
    /// </summary>
    /// <typeparam name="T">容器类名</typeparam>
    /// <returns></returns>
    public T GetTable<T>() where T : class
    {
        string tableName = typeof(T).Name;
        if(tableDic.ContainsKey(tableName))
        {
            return tableDic[tableName] as T;
        }
        return null;
    }

    /// <summary>
    /// 存储类对象数据
    /// </summary>
    /// <param name="data"></param>
    /// <param name="fileName"></param>
    public void Save(object data, string fileName)
    {
        if(!Directory.Exists(SAVE_PATH))
        {
            Directory.CreateDirectory(SAVE_PATH);
        }

        using(FileStream fs = new FileStream(SAVE_PATH + fileName + ".zhou", FileMode.OpenOrCreate, FileAccess.Write))
        {
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(fs, data);

            fs.Flush();
            fs.Close();
        }
    }

    /// <summary>
    /// 读取2进制数据转换成对象
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public T Load<T>(string fileName) where T : class
    {
        if(!File.Exists(SAVE_PATH + fileName + ".zhou"))
        {
            return default(T);
        }

        T data;
        using(FileStream fs = new FileStream(SAVE_PATH + fileName + ".zhou", FileMode.Open, FileAccess.Read))
        {
            BinaryFormatter bf = new BinaryFormatter();
            data = bf.Deserialize(fs) as T;
            fs.Close();
        }
        return data;
    }

}

3、使用步骤

3.1 根据规则创建Excel配置表

创建的Excel表需要放置到ArtRes/Excel路径下

Excel配置规则

  • 第一行:字段名
  • 第二行:字段类型(字段类型一定不要配置错误,字段类型目前只支持int float bool string)如果想要在添加类型,需要ExcelTool的GenerateExcelBianary和BianryDataMgr的LoadTable方法当中对应添加读写的逻辑
  • 第三行:主键是哪一个字段 需要通过key来标识主键
  • 第四行:描述信息(只是给别人看,不会有别的作用)
  • 第五行~第n行:就是具体数据信息
  • 下方的表名决定类数据结构类,容器类,2进制文件的文件名

3.2 点击按钮GenerateExcelInfo

生成所有Excel表的数据结构类、数据结构容器类和二进制数据文件

3.3 完善BinaryDataMgr的InitData()方法,将自己创建的表进行加载

cs 复制代码
    public void InitData()
    {
        //加载自己的表到内存
        //LoadTable<TowerInfoContainer, TowerInfo>();
        //LoadTable<PlayerInfo1Container, PlayerInfo1>();
        //LoadTable<TestInfoContainer, TestInfo>();
        LoadTable<WeaponItemContainer, WeaponItem>();
    }

3.4 初始化数据,加载数据到内存

BinaryDataMgr.Instance.InitData() 加载二进制数据到内存当中

BinaryDataMgr.Instance.GetTable<TowerInfoContainer>() 获取需要使用的表的数据

cs 复制代码
        BinaryDataMgr.Instance.InitData();
        WeaponItemContainer data = BinaryDataMgr.Instance.GetTable<WeaponItemContainer>();

        foreach (var item in data.dataDic.Values)
        {
            Debug.Log($"id:{item.id}-name:{item.name}-des:{item.des}-money:{item.money}-ATK:{item.atk}");
        }
相关推荐
热心市民运维小孙11 小时前
Ubuntu重命名默认账户
linux·ubuntu·excel
超龄魔法少女18 小时前
[Unity] ShaderGraph动态修改Keyword Enum,实现不同效果一键切换
unity·技术美术·shadergraph
又蓝19 小时前
使用 Python 操作 Excel 表格
开发语言·python·excel
蔗理苦20 小时前
2024-12-24 NO1. XR Interaction ToolKit 环境配置
unity·quest3·xr toolkit
花生糖@20 小时前
Android XR 应用程序开发 | 从 Unity 6 开发准备到应用程序构建的步骤
android·unity·xr·android xr
向宇it20 小时前
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
开发语言·unity·c#·编辑器·游戏引擎
m0_748230941 天前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端·rust·excel
Swift社区1 天前
Excel 列名称转换问题 Swift 解答
开发语言·excel·swift
向宇it1 天前
【从零开始入门unity游戏开发之——unity篇01】unity6基础入门开篇——游戏引擎是什么、主流的游戏引擎、为什么选择Unity
开发语言·unity·c#·游戏引擎
neeef_se1 天前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel