【Unity】Excel导出工具(二)
前言
上一篇简单的制作了导出工具的UI界面,那么本篇就来解决excel读取到DataTable后,怎么把这些数据序列化为二进制,以及序列化成功后在游戏中怎么再还原回对应的数据类型的对象这两个问题,完成C#代码和数据的生成。
数据序列化和反序列化
关于什么是序列化什么是反序列化属于常识性问题,这里就不进行解释了(实在不知道就baidu一下、google一下)下面就详细的阐述从策划配置完excel到在游戏中应用这些数据的整个过程。
序列化
使用MemoryStream进行流式的数据读写显然是简单明了且效率极高的一种做法。比起一个一个的手动转换类型然后再进行繁琐的数组写入,这种方式只需要逐个把数据写进流中,写完后调用ToArray()就得到了一个转换好的bytes数组,再把得到的数组直接用文件流写入文件就完成了整个工作。要实现这种序列化方法,需要扩展MemoryStream把数据类型转化的代码封装在使用MemoryStream进行读写之前,即:先把数据转为byte[],再写入流中。于是这里要实现一个MemoryStreamEx,它可以用于各种场景的数据二进制序列化和反序列化。
1 | public class MemoryStreamEx : MemoryStream |
可以看到,不同类型反序列化的时候读取的字节长度都不相同,其实这些计算机基础里都有讲过:int(int32)占4个字节=32个二进制位,short(int16)占2个字节=16个二进制位,long(int64)占8个二进制位=64个二进制位,bool占一个字节=8个二进制位,byte=一个字节=8个二进制位……等等,而这里的数据都是以byte(字节)作为单位的,而像读取int的代码中base.Read(arr, 0, 4);长度为什么是4就不难理解了。
经过上面操作,数据序列化为二进制已经解决了,接下来还要再写入些什么才能在游戏中把它们顺利的读取出来呢?在TCP通信的文章中有介绍过,解决粘包的方法是在包头写入包体长度,那这里是否也可以计算一个数据的总长把它写入到包头呢,甚至把它的类型也写入?其实只要认真观察一下excel的配置格式就看得出,每一列的数据类型都是一样的,而所有的数据类型的声明都在第二行,如果每个数据都写入长度和类型难免会显得有些冗余,所以只需要计算第四行以后的包含真实数据的总行数总列数写入包头,再把数据全部写入,读取的时候先读出行列数,根据这个行列数逐行逐列读取就可以了。
PS:包含数据的总行数=excel表总行数 - 3,前四行除了第一行的字段名其他的都不需要写入;总列数=excel表总列数 - 1,第一列不需要写入。
1 | protected override void ExportData(DataTable dt, string excelName, string sheetName) |
序列化的核心代码就是这些,整个流程为:
写入行数
写入列数
写入字段名
把所有的数据全部转换为string并写入
压缩
写入到文件中
反序列化
经过上面序列化操作后就得到一个写满二进制数据的bytes文件,而想要在游戏中使用它就需要把它的数据逐个读取出来。有了上面序列化的基础,反序列化只需要把上面序列化的操作倒过来即可。这里就可以再实现一个ConfigDataParser用于游戏中的反序列化。
1 | public class ConfigDataParser : IDisposable |
整个反序列化核心逻辑都在构造函数里面,过程为:
解压缩
读取行数
读取列数
读取字段名
读取数据
使用的时候先读取出文件内容,然后创建ConfigDataParser对象并传入数据,用eof字段判断是否到达文件结尾,未到达则循环创建数据对象,类似下面这样:
1 | public static T[] LoadConfigData<T>(string filePath, string fileName) where T : BaseConfigData, new() |
这里的BaseConfigData就是一个抽象类,定义了一个id字段方法Read(),工具生成的代码都要继承它并重写Read方法。
1 | public abstract class BaseConfigData |
代码生成
有了数据后还需要把这些数据还原成对应的类的对象才能应用到游戏中。实际上,策划在excel中配置的数据类型是多种多样的,几乎会涵盖int,float,double,long,string,bool等所有类型,而一旦把一个数据转为二进制,那它原来是什么类型就不知道了,这样就很难还原回去。
此时再回过头去观察excel,又不难发现,其实在excel中前三行已经分别标注了数据的字段名、类型、注释。上面在序列化的时候把所有的数据都转为了string类型,那么它原本是什么类型,只需要在生成的代码中加入一个把string转为excel中标注的类型的方法就解决了这个问题。这个功能的实现其实还是比较简单的,直接贴代码吧。
1 | private void CreateDataScript(DataTable dt, string excelName, string sheetName, string dataTableName) |
另外json的解析简单写一下,先把整个表的数据遍历一次过滤出json的字符串加入到一个字典中,再遍历这些json字符串把他们逐个反序列化为对象,然后判断反序列化出来的对象如果是数组就再继续遍历并逐个递归,如果是键值对先判断子阶段是否存在同名字段,不存在就读出key和value生成代码。具体的不多写了,这个功能用的不多,而且太复杂的树、图结构也无法正确解析。
1 | //json解析代码 |
导出屏蔽
现在可以在导出之前加入屏蔽功能了,在上一篇说过:
第一行第一列留空,其后为字段名
第二行第一列为配置表名,其后为字段类型
第三行第一列留空,其后可以留空或者填入字段含义
第四行第一列填入“BAN”则整张表不导出,其后填入“BAN”则对应列整列不导出
第五行开始,每行第一列填入“BAN”则整行不导出,其后为具体数据
以上填入“BAN”的位置若没有不导出的需求则留空
要实现这个BAN的功能,只需在导出时进行过滤即可。导出时,若表的“第四行第一列填入BAN”则直接跳出导出方法不进行导出,否则先逐列遍历,把“第四行第二列及以后填入BAN”的列全部移除,再逐行遍历,把“第五行开始,每行第一列填入BAN”的行全部移除。经过这几次筛选,剩下的就是需要导出的表了。
1 | private void Export(string filePath) |
结语
C#的数据生成和代码生成就写完了,下一篇实现Lua的代码生成然后进行测试。