【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>();
相关推荐
阿萨德528号9 分钟前
Maven 项目构建笔记 - 单体应用与简单微服务
笔记·微服务·maven
YJlio12 分钟前
Strings 学习笔记(12.1):从二进制里“扒”出明文信息的瑞士军刀
服务器·笔记·学习
Hunter11632 分钟前
Delphi通过ITHTTP传输有汉字乱码问题
笔记
有为少年42 分钟前
带噪学习 | Ambient Diffusion (NeurIPS 2023)下篇
人工智能·深度学习·神经网络·学习·机器学习·计算机视觉
代码游侠1 小时前
复习——线程(pthread)
linux·运维·开发语言·网络·学习·算法
做cv的小昊1 小时前
【TJU】信息检索与分析课程笔记和练习(3)学术评价
大数据·人工智能·经验分享·笔记·学习·全文检索
兜兜转转了多少年1 小时前
《Prompt Engineering白皮书》笔记08 我用 Gemini 10 分钟写完脚本,100 个文件自动改名
笔记·prompt
一线灵1 小时前
Axmol 开发环境快速搭建指南 (新)
游戏引擎
重生之我在番茄自学网安拯救世界2 小时前
网络安全中级阶段学习笔记(九):upload靶场实战(14-16关)-图片马制作与通过教学
笔记·学习·网络安全·文件上传漏洞·图片木马
Lv11770082 小时前
Visual Studio中的静态成员
笔记·c#·visual studio