【Unity】利用二进制数据持久化 【练习学习项目/有不足之处欢迎斧正/侵删】

1.为编辑器菜单栏添加新的选项入口

通过Unity提供的MenuItem特性在菜单栏添加选项按钮

特性名:MenuItem

命名空间:UnityEditor

要求:一定是静态方法;新建的这个菜单栏按钮 必须有至少一个斜杠 不然会报错 它不支持只有一个菜单栏入口 ;这个特性可以用在任意的类当中

cs 复制代码
    [MenuItem("GameTool/Test")]
    private static void Test()
    {

        Directory.CreateDirectory(Application.dataPath + "/测试文件夹");

        AssetDatabase.Refresh();
        
    }

同时,通过以上方式,可以调用后自动刷新窗口

类名:AssetDatabase

命名空间:UnityEditor

方法:Refresh

补充:Editor文件夹

Editor文件夹可以放在项目的任何文件夹下,可以有多个,放在其中的内容,项目打包时不会被打包到项目中。一般编辑器相关代码都可以放在该文件夹中

同时不能再非editor的文件夹下的脚本调用editor文件夹下的脚本

2.导入ExcelDll包

Excel表的本质:Excel表本质上也是一堆数据,有自己的存储读取规则

DLL文件用来解析Excel文件的

Dll文件:库文件,可以理解为它是许多代码的集合,将相关代码集合在库文件中可以方便迁移和使用,方便用户直接使用

(建议放在editor文件夹下)

3.Excel数据读取

利用FileStream读取文件流,再利用IExcelDataReader类,从流中读取Excel数据,使用DataSet数据集合类,将Excel数据转存进其中方便读取

cs 复制代码
        using (FileStream fs = File.Open(Application.dataPath + "/ArtRes/Excel/PlayerInfo.xlsx", FileMode.Open, FileAccess.Read ))
        {
            //通过文件流获取Excel数据
            IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(fs);
            //将excel表中的数据转换为DataSet数据类型 方便获取其中的内容
            DataSet result = excelReader.AsDataSet();
            //得到Excel文件中的所有表信息
            for (int i = 0; i < result.Tables.Count; i++)
            {
                Debug.Log("表名:" + result.Tables[i].TableName);
                Debug.Log("行数:" + result.Tables[i].Rows.Count);
                Debug.Log("列数:" + result.Tables[i].Columns.Count);
            }
            fs.Close();
        }

获取Excel表中单元格的信息

cs 复制代码
using (FileStream fs = File.Open(Application.dataPath + "/ArtRes/Excel/PlayerInfo.xlsx", FileMode.Open, FileAccess.Read))
        {
            IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(fs);
            DataSet result = excelReader.AsDataSet();

            for (int i = 0; i < result.Tables.Count; i++)
            {
                
                DataTable table = result.Tables[i];


                DataRow row;
                for (int j = 0; j < table.Rows.Count; j++)
                {
                    //得到每一行的信息
                    row = table.Rows[j];

                    for (int k = 0; k < table.Columns.Count; k++)
                    {
                        Debug.Log(row[k].ToString());
                    }
                }
            }

            fs.Close();
        }

DataTable 数据表类:表示Excel文件中的一个表

DataRow 数据行类:表示某张表中的一行数据

可以根据表中数据来动态的生成相关数据:数据结构类;容器类;2进制数据

为什么不直接读取Excel表而要把它转成2进制数据?

提升读取效率和数据安全性

4.制定配置表的相关规则

数据结构类

容器:放一个字典,存储整张表的数据,对应数据结构类的类型,key利用唯一ID来设置(保证主键不一样)

二进制数据

5.读取Excel目录下所有的excel文件

声明文件存放的路径

cs 复制代码
public static string EXCEL_PATH = Application.dataPath + "/ArtRes/Excel/";

加载指定路径中的所有Excel文件,用于生成对应的3个文件

cs 复制代码
        DirectoryInfo dInfo = Directory.CreateDirectory(EXCEL_PATH);
        //得到指定路径中的所有文件信息 相当于就是得到所有的Excel表
        FileInfo[] files = dInfo.GetFiles();

遍历数组中所有的内容,其中会有meta文件,之后操作中无需对其进行处理

cs 复制代码
for (int i = 0; i < files.Length; i++)
        {
            
            if (files[i].Extension != ".xlsx" &&
                files[i].Extension != ".xls")
                continue;
}

files[i].Extension 记录的是文件的后缀名

设置数据类容器

cs 复制代码
DataTableCollection tableConllection;

打开一个Excel文件得到其中的所有表的数据

cs 复制代码
            using (FileStream fs = files[i].Open(FileMode.Open, FileAccess.Read))
            {
                IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(fs);
                tableConllection = excelReader.AsDataSet().Tables;
                fs.Close();
            }

遍历文件中的所有表的信息

cs 复制代码
            foreach (DataTable table in tableConllection)
            {
                Debug.Log(table.TableName);

            }

6.生成数据结构类

创建生成Excel表对应的数据结构类

cs 复制代码
    private static void GenerateExcelDataClass(DataTable table)
    {

    }

获取变量名所在行

cs 复制代码
    private static DataRow GetVariableNameRow(DataTable table)
    {
        return table.Rows[0];
    }

获取变量类型所在行

cs 复制代码
    private static DataRow GetVariableTypeRow(DataTable table)
    {
        return table.Rows[1];
    }

回到生成Excel表对应的数据结构类

cs 复制代码
   private static void GenerateExcelDataClass(DataTable table)
    {
        //字段名行
        DataRow rowName = GetVariableNameRow(table);
        //字段类型行
        DataRow rowType = GetVariableTypeRow(table);

    }

设置数据结构类脚本存储位置路径

cs 复制代码
public static string DATA_CLASS_PATH = Application.dataPath + "/Scripts/ExcelData/DataClass/";

生成对应的数据结构类脚本(通过代码进行字符串拼接 然后存进文件)

cs 复制代码
        //判断路径是否存在 没有的话 就创建文件夹
        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 += "}";

把拼接好的字符串存到指定文件中去

cs 复制代码
 File.WriteAllText(DATA_CLASS_PATH + table.TableName + ".cs", str);

最后刷新Project窗口

cs 复制代码
AssetDatabase.Refresh();

7.生成容器类

利用字典,表示一张表的数据

创建Excel表对应的数据容器类

cs 复制代码
    private static void GenerateExcelContainer(DataTable table)
    {

    }

获取主键索引

cs 复制代码
    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;
    }

获取变量类型所在行

cs 复制代码
    private static DataRow GetVariableTypeRow(DataTable table)
    {
        return table.Rows[1];
    }

得到主键索引

cs 复制代码
int keyIndex = GetKeyIndex(table);

得到字段类型行

cs 复制代码
DataRow rowType = GetVariableTypeRow(table);

设置容器类脚本存储位置路径

cs 复制代码
public static string DATA_CONTAINER_PATH = Application.dataPath + "/Scripts/ExcelData/Container/";

具体容器类内部逻辑

cs 复制代码
        //没有路径创建路径
        if (!Directory.Exists(DATA_CONTAINER_PATH))
            Directory.CreateDirectory(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(DATA_CONTAINER_PATH + table.TableName + "Container.cs", str);

刷新Project窗口

cs 复制代码
        AssetDatabase.Refresh();

8.生成二进制数据

生成excel 2进制数据

cs 复制代码
    private static void GenerateExcelBinary(DataTable table)
    {
    }

可以利用StreamingAssets在非编辑模式下,是只读状态

2进制数据存储位置路径

cs 复制代码
public static string DATA_BINARY_PATH = Application.streamingAssetsPath + "/Binary/";

创建一个2进制文件进行写入

cs 复制代码
FileStream fs = new FileStream(DATA_BINARY_PATH + table.TableName + ".yuan", FileMode.OpenOrCreate, FileAccess.Write)

存储具体的excel对应的2进制信息

存储我们需要写多少行的数据,方便读取

cs 复制代码
fs.Write(BitConverter.GetBytes(table.Rows.Count - 4), 0, 4);

存储主键的变量名

cs 复制代码
            string keyName = GetVariableNameRow(table)[GetKeyIndex(table)].ToString();
            byte[] bytes = Encoding.UTF8.GetBytes(keyName);

存储字符串字节数组的长度

cs 复制代码
fs.Write(BitConverter.GetBytes(bytes.Length), 0, 4);

存储字符串字节数组

cs 复制代码
fs.Write(bytes, 0, bytes.Length);

得到类型行 根据类型来决定应该如何写入数据

cs 复制代码
DataRow rowType = GetVariableTypeRow(table);

遍历所有内容的行 进行2进制的写入

cs 复制代码
            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();

最后别忘了刷新Project窗口

cs 复制代码
AssetDatabase.Refresh();

9.Excel数据文件的使用

创建用于存储所有Excel表数据的容器

cs 复制代码
private Dictionary<string, object> tableDic = new Dictionary<string, object>();

数据存储的位置

cs 复制代码
private static string SAVE_PATH = Application.persistentDataPath + "/Data/";

读取excel表对应的2进制文件,根据结构体类名进行判断

cs 复制代码
using (FileStream fs = File.Open(DATA_BINARY_PATH + typeof(K).Name + ".tang", FileMode.Open, FileAccess.Read)){}

记录读取信息

cs 复制代码
            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;

创建容器类对象(通过反射)

cs 复制代码
            Type contaninerType = typeof(T);
            object contaninerObj = Activator.CreateInstance(contaninerType);

(Activator根据type创建对象)

得到数据结构类的Type

cs 复制代码
Type classType = typeof(K);

通过反射来得到数据结构类所有字段的信息

cs 复制代码
FieldInfo[] infos = classType.GetFields();

读取每一行的信息

cs 复制代码
            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;
                    }
                }

每次读取完一行的数据,便把这个数据添加到容器对象中

cs 复制代码
                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 });

把读取完的表记录下来

cs 复制代码
            tableDic.Add(typeof(T).Name, contaninerObj);
            fs.Close();

10.工具包导出

书写相关帮助文档

创建测试工程、

cs 复制代码
        BinaryDataMgr.Instance.InitData();


        TowerInfoContainer data = BinaryDataMgr.Instance.GetTable<TowerInfoContainer>();
相关推荐
Nu11PointerException1 小时前
JAVA笔记 | ResponseBodyEmitter等异步流式接口快速学习
笔记·学习
亦枫Leonlew3 小时前
三维测量与建模笔记 - 3.3 张正友标定法
笔记·相机标定·三维重建·张正友标定法
考试宝3 小时前
国家宠物美容师职业技能等级评价(高级)理论考试题
经验分享·笔记·职场和发展·学习方法·业界资讯·宠物
黑叶白树4 小时前
简单的签到程序 python笔记
笔记·python
@小博的博客5 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
幸运超级加倍~5 小时前
软件设计师-上午题-15 计算机网络(5分)
笔记·计算机网络
南宫生5 小时前
贪心算法习题其四【力扣】【算法学习day.21】
学习·算法·leetcode·链表·贪心算法
懒惰才能让科技进步6 小时前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
love_and_hope6 小时前
Pytorch学习--神经网络--搭建小实战(手撕CIFAR 10 model structure)和 Sequential 的使用
人工智能·pytorch·python·深度学习·学习
Chef_Chen6 小时前
从0开始学习机器学习--Day14--如何优化神经网络的代价函数
神经网络·学习·机器学习