C# Unity游戏开发——Excel中的数据是如何到游戏中的 (二)
本帖是延续的:C# Unity游戏开发——Excel中的数据是如何到游戏中的 (一)
上个帖子主要是讲了如何读取Excel,本帖主要是讲述读取的Excel数据是如何序列化成二进制的,考虑到现在在手游中应用很广泛的序列化技术Google的ProtoBuf,所以本文也是按照ProtoBuf的方式来操作的。ProtoBuf是一个开源库,简单来说ProtoBuf就是一个能使序列化的数据变得更小的类库,当然这里指的更小是相对的。好了ProtBuf的东西就不在多说,以后会专门写一篇帖子的。本帖其实就相当于上帖中列出的“流程”中的2.将读取的数据进行序列化并写入文件。虽然这句话只有16个字,但是他的内容绝对不止16个字这么简单。
一开始计划的是这样的:1.先把Excel数据读出来。2.然后根据数据生成相应的类代码。3.然后再生成代码的实例并且通过反射(Reflection)把Excel中的数据填进去。4.最后用ProtBuf进行序列化。这听起来似乎很合理,不过在实现过程中还是遇到了几个棘手的问题。但是最重要的一个问题是,3.生成代码的实例并且通过反射(Reflection)把Excel中的数据填进去,中“生成实例”的过程。
在运行时生成的代码知识一串字符而已,想要生成字符串中的类的实例需要用到动态编译把代码编译后生成一个Assembly,然后用Assembly中的CreateInstance方法创建一个object类型的实例,这个实例就是字符串代码中的类的实例。但是,我们使用了ProtoBuf数据的格式来生成代码,所以工程中直接把ProtBuf的源码放进来而不是引入protobuf-net.dll,所以在动态编译设置编译参数来动态引入DLL文件的时候经常出错而导致,编译生成的Assembly是null。所以考虑到这些本文使用了一种折中的方案来跳过动态编译这个过程。
因为在Unity项目中我们关注的只是最终生成的二进制文件本身所以对于它是如何生成的对于Unity项目来说是没有任何影响的,只要保证数据的格式和生成代码的格式相对应一切就OK。基于此,我们把这个过程分成了两部分,1.生成代码。2.生成二进制文件(其实就是把编译的工作交给了编译器。。。)OK,话不多说了,帖代码吧!
1.生成代码
public static void GenerateCode() { FileInfo info; FileStream stream; IExcelDataReader excelReader; DataSet result; string[] files = Directory.GetFiles(Application.dataPath + "/EasyUI/ExcelFiles", "*.xlsx", SearchOption.TopDirectoryOnly); string staticDataClassCode = ""; try { int priority1 = 1; string code; foreach (string path in files) { info = new FileInfo(path); stream = info.Open(FileMode.Open, FileAccess.Read); excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream); result = excelReader.AsDataSet(); int rowCount = result.Tables[0].Rows.Count; int colCount = result.Tables[0].Columns.Count; string className = result.Tables[1].Rows[2][0].ToString(); staticDataClassCode += " [ProtoMember(" + priority1++ + ")]\n"; staticDataClassCode += " public List<" + className + "> " + className + "List = new List<" + className + ">();\n"; code = ""; code += "using System.Collections;\n"; code += "using System.Collections.Generic;\n"; code += "using ProtoBuf;\n"; code += "[ProtoContract]\n"; code += "public class " + className + "\n"; code += "{\n"; int priority2 = 1; for (int col = 0; col < colCount; col++) { code += " [ProtoMember(" + priority2++ + ")]\n"; code += " public " + result.Tables[1].Rows[1][col].ToString() + " " + result.Tables[1].Rows[0][col].ToString() + ";\n"; } code += " public " + className + "()\n"; code += " {}\n"; code += "}\n"; WriteClass(Application.dataPath + "/Script/Datas/" + className + ".cs", className, code); excelReader.Close(); stream.Close(); } code = ""; code += "using System.Collections;\n"; code += "using System.Collections.Generic;\n"; code += "using ProtoBuf;\n"; code += "[ProtoContract]\n"; code += "public class StaticData\n"; code += "{\n"; code += staticDataClassCode; code += " public StaticData(){}\n"; code += "}\n"; WriteClass(Application.dataPath + "/Script/Datas/StaticData.cs", "StaticData", code); } catch (IndexOutOfRangeException exp) { Debug.LogError(exp.StackTrace); } AssetDatabase.Refresh(); AssetDatabase.SaveAssets(); }
2..生成二进制文件
public static void GenerateBinFile() { FileInfo info; FileStream stream; IExcelDataReader excelReader; DataSet result; string[] files = Directory.GetFiles(Application.dataPath + "/EasyUI/ExcelFiles", "*.xlsx", SearchOption.TopDirectoryOnly); int row = 0, col = 0; string name = "", value = "", type = ""; StaticData staticData = new StaticData(); foreach (string path in files) { info = new FileInfo(path); stream = info.Open(FileMode.Open, FileAccess.Read); excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream); result = excelReader.AsDataSet(); int rowCount = result.Tables[0].Rows.Count; int colCount = result.Tables[0].Columns.Count; string className = result.Tables[1].Rows[2][0].ToString(); FieldInfo field = staticData.GetType().GetField(className + "List");//获取类中的一个Field object fieldValue = field.GetValue(staticData);//给这个实例中的Field的代表的属性赋值 IList list = fieldValue as IList; for (row = 1; row < rowCount; row++) { Type tt = DataManager.Instance.GetType(className); object obj = System.Activator.CreateInstance(tt); for (col = 0; col < colCount; col++) { name = result.Tables[1].Rows[0][col].ToString(); value = result.Tables[0].Rows[row][col].ToString(); type = result.Tables[1].Rows[1][col].ToString(); Debug.Log(className); FieldInfo ifo = tt.GetField(name); object cvalue = System.ComponentModel.TypeDescriptor.GetConverter(ifo.FieldType).ConvertFrom(value); ifo.SetValue(obj, cvalue); } if (list != null) list.Add(obj); } excelReader.Close(); stream.Close(); } using (FileStream fstream = File.Create(Application.dataPath + "/StaticDatas.bin")) { Serializer.Serialize(fstream, staticData); } AssetDatabase.Refresh(); AssetDatabase.SaveAssets(); }
说明一下DataManager.Instance.GetType(className);
Type.GetType(string)会默认从当前程序及搜索类型,而我的类文件是放到了Unity的Editor文件下,Unity会把这个文件识别为属于Editor程序集,但是生成的代码是放到了其他文件夹下Unity会把文件识别为另一个程序集,这个DataManager一定要放到和生成的类文件的相同程序集中。代码如下
using UnityEngine; using System.Collections; using System; public class DataManager { private static DataManager _instance; public static DataManager Instance { get { if (_instance == null) _instance = new DataManager(); return _instance; } } public DataManager() { _instance = this; } public Type GetType(string name) { return Type.GetType(name); } }
将代码写入文件的方法
private static void WriteClass(string path,string className,string code) { System.IO.File.WriteAllText(path, code, System.Text.UnicodeEncoding.UTF8); }
把动态编译代码的方法也贴出来吧,方便以后用到
private static Assembly compileCode(string fullClassName, string code) { //创建编译器 CSharpCodeProvider provider = new CSharpCodeProvider(); //设置编译参数 CompilerParameters paras = new CompilerParameters(); //paras.ReferencedAssemblies.Add("System.Collections.dll"); //paras.ReferencedAssemblies.Add("System.Collections.Generic.dll"); paras.ReferencedAssemblies.Add("System.dll"); paras.ReferencedAssemblies.Add("protobuf-net.dll"); //paras.OutputAssembly = "ScriptData.dll"; //编译成exe还是dll paras.GenerateExecutable = false; //是否写入内存,不写入内存就写入磁盘 paras.GenerateInMemory = true; CompilerResults result = provider.CompileAssemblyFromSource(paras, code);//编译代码 Assembly as1 = result.CompiledAssembly; //获得编译后的程序集 return as1; }
OK,文件已经生成了,经测试两个10K的Excel文件打包成一个StaticData.bin文件,1K!!!
不过一般游戏中的数据表无论是内容还是数量都远远大于这个数量。所以一般生成的bin文件还要进行压缩,或者用Unity打包,哦了,就先到这把剩下的内容下一帖继续。
http://m.oschina.net/blog/308267
http://dsqiu.iteye.com/blog/1887702
http://www.cnblogs.com/zfanlong1314/p/4197383.html
http://stackoverflow.com/questions/2522376/how-to-choose-between-protobuf-csharp-port-and-protobuf-net