Unity C#配置表工具

转载:https://blog.csdn.net/qq_14903317/article/details/78637440

一般在游戏开发中策划都会把数据配置在excel中.所以我们需要从excel中导出数据,并且把数据保存在本地.

有很多种方式可以把数据导出成我们想要的格式,比如说导出为json,cs,或者xml.不过我更喜欢直接把数据序列化为二进制文件.然后在游戏中加载的时候直接反序列化就可以了.这样引用数据的时候非常方便.


首先说下流程.

1. 从excel中读取数据
2. 根据数据类型.动态生成每个表的C#类
3. 动态编译C#类.然后输出为一个动态库
4. 实例化C#类,并且把数据填入到实例化的对象中
5. 序列化数据,保存在Unity中的Resources目录中
6. 在Unity中引用之前输出的动态库,在游戏运行时加载数据.并且进行反序列化


先来看看配置表的格式:

这里写图片描述


在上面的配置表中,前三行为我预留的位置,第四行和第五行分别表示这个字段在类中的类型和成员变量名
格式定好了,那我们就按照格式把数据从excel中读取出来就行了.


            string[] files = Directory.GetFiles(excelPath, "*.xlsx");
            List<string> codeList = new List<string>();
            Dictionary<string, List<ConfigData[]>> dataDict = new Dictionary<string, List<ConfigData[]>>();
            for (int i = 0; i < files.Length; ++i)
            {
                //打开excel
                string file = files[i];
                FileStream stream = File.Open(file, FileMode.Open, FileAccess.Read);
                IExcelDataReader excelReader = ExcelReaderFactory.CreateOpenXmlReader(stream);
                if (!excelReader.IsValid)
                {
                    Console.WriteLine("invalid excel " + file);
                    continue;
                }
 
 

    这里是开始在读取excel表.我引用了一个第三方库来读取excel.各位可以用其它方法来读取.只要能读取到每一行的数据,那都是一样的.

        class ConfigData
        {
            public string Type;
            public string Name;
            public string Data;
        }
     
     

      首先我定义了一个类,每一个字段都会实例化一个这个类.并且把类型和名称以及数据保存在这个类中.

                      string[] types = null;
                      string[] names = null;
                      List<ConfigData[]> dataList = new List<ConfigData[]>();
                      int index = 1;
      
                      //开始读取
                      while (excelReader.Read())
                      {
                          //这里读取的是每一行的数据
                          string[] datas = new string[excelReader.FieldCount];
                          for (int j = 0; j < excelReader.FieldCount; ++j)
                          {
                              datas[j] = excelReader.GetString(j);
                          }
      
                          //空行不处理
                          if (datas.Length == 0 || string.IsNullOrEmpty(datas[0]))
                          {
                              ++index;
                              continue;
                          }
      
                          //第三行表示类型
                          if (index == PROPERTY_TYPE_LINE) types = datas;
                          //第四行表示变量名
                          else if (index == PROPERTY_NAME_LINE) names = datas;
                          //后面的表示数据
                          else if (index > PROPERTY_NAME_LINE)
                          {
                              //把读取的数据和数据类型,名称保存起来,后面用来动态生成类
                              List<ConfigData> configDataList = new List<ConfigData>();
                              for (int j = 0; j < datas.Length; ++j)
                              {
                                  ConfigData data = new ConfigData();
                                  data.Type = types[j];
                                  data.Name = names[j];
                                  data.Data = datas[j];
                                  if (string.IsNullOrEmpty(data.Type) || string.IsNullOrEmpty(data.Data))
                                      continue;
                                  configDataList.Add(data);
                              }
                              dataList.Add(configDataList.ToArray());
                          }
                          ++index;
                      }
       
       
                        //类名
                        string className = excelReader.Name;
                        //根据刚才的数据来生成C#脚本
                        ScriptGenerator generator = new ScriptGenerator(className, names, types);
                        //所有生成的类最终保存在这个链表中
                        codeList.Add(generator.Generate());
                        if (dataDict.ContainsKey(className)) Console.WriteLine("相同的表名 " + className);
                        else dataDict.Add(className, dataList);
         
         
           //脚本生成器
              class ScriptGenerator
              {
                  public string[] Fileds;
                  public string[] Types;
                  public string ClassName;
          
                  public ScriptGenerator(string className, string[] fileds, string[] types)
                  {
                      ClassName = className;
                      Fileds = fileds;
                      Types = types;
                  }
          
                  //开始生成脚本
                  public string Generate()
                  {
                      if (Types == null || Fileds == null || ClassName == null)
                          return null;
                      return CreateCode(ClassName, Types, Fileds);
                  }
          
                  //创建代码。   
                  private string CreateCode(string tableName, string[] types, string[] fields)
                  {
                      //生成类
                      StringBuilder classSource = new StringBuilder();
                      classSource.Append("/*Auto create\n");
                      classSource.Append("Don't Edit it*/\n");
                      classSource.Append("\n");
                      classSource.Append("using System;\n");
                      classSource.Append("using System.Reflection;\n");
                      classSource.Append("using System.Collections.Generic;\n");
                      classSource.Append("[Serializable]\n");
                      classSource.Append("public class " + tableName + "\n");
                      classSource.Append("{\n");
                      //设置成员
                      for (int i = 0; i < fields.Length; ++i)
                      {
                          classSource.Append(PropertyString(types[i], fields[i]));
                      }
                      classSource.Append("}\n");
          
                      //生成Container
                      classSource.Append("\n");
                      classSource.Append("[Serializable]\n");
                      classSource.Append("public class " + tableName + "Container\n");
                      classSource.Append("{\n");
                      classSource.Append("\tpublic " + "Dictionary<int, " + tableName + ">" + " Dict" + " = new Dictionary<int, " + tableName + ">();\n");
                      classSource.Append("}\n");
                      return classSource.ToString();
                  }
          
                  private string PropertyString(string type, string propertyName)
                  {
                      if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(propertyName))
                          return null;
          
                      if (type == SupportType.LIST_INT) type = "List<int>";
                      else if (type == SupportType.LIST_FLOAT) type = "List<float>";
                      else if (type == SupportType.LIST_STRING) type = "List<string>";
                      StringBuilder sbProperty = new StringBuilder();
                      sbProperty.Append("\tpublic " + type + " " + propertyName + ";\n");
                      return sbProperty.ToString();
                  }
              }
           
           

            这个类用于生成配置表类代码.

                        //编译代码,序列化数据
                        Assembly assembly = CompileCode(codeList.ToArray(), null);
                        string path = _rootPath + _dataPath;
                        if (Directory.Exists(path)) Directory.Delete(path, true);
                        Directory.CreateDirectory(path);
                        foreach (KeyValuePair<string, List<ConfigData[]>> each in dataDict)
                        {
                            object container = assembly.CreateInstance(each.Key + "Container");
                            Type temp = assembly.GetType(each.Key);
                            Serialize(container, temp, each.Value, path);
                        }
             
             

              得到了生成的类代码过后.我们需要动态编译这些代码.并且填充数据.

                      //编译代码
                      private static Assembly CompileCode(string[] scripts, string[] dllNames)
                      {
                          string path = _rootPath + _dllPath;
                          if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                          //编译参数
                          CSharpCodeProvider codeProvider = new CSharpCodeProvider();
                          CompilerParameters objCompilerParameters = new CompilerParameters();
                          objCompilerParameters.ReferencedAssemblies.AddRange(new string[] { "System.dll" });
                          objCompilerParameters.OutputAssembly = path + "Config.dll";
                          objCompilerParameters.GenerateExecutable = false;
                          objCompilerParameters.GenerateInMemory = true;
              
                          //开始编译脚本
                          CompilerResults cr = codeProvider.CompileAssemblyFromSource(objCompilerParameters, scripts);
                          if (cr.Errors.HasErrors)
                          {
                              Console.WriteLine("编译错误:");
                              foreach (CompilerError err in cr.Errors)
                                  Console.WriteLine(err.ErrorText);
                              return null;
                          }
                          return cr.CompiledAssembly;
                      }
               
               
                        //序列化对象
                        private static void Serialize(object container, Type temp, List<ConfigData[]> dataList, string path)
                        {
                            //设置数据
                            foreach (ConfigData[] datas in dataList)
                            {
                                object t = temp.Assembly.CreateInstance(temp.FullName);
                                foreach (ConfigData data in datas)
                                {
                                    FieldInfo info = temp.GetField(data.Name);
                                    info.SetValue(t, ParseValue(data.Type, data.Data));
                                }
                
                                object id = temp.GetField("id").GetValue(t);
                                FieldInfo dictInfo = container.GetType().GetField("Dict");
                                object dict = dictInfo.GetValue(container);
                
                                bool isExist = (bool)dict.GetType().GetMethod("ContainsKey").Invoke(dict, new Object[] {id});
                                if (isExist)
                                {
                                    Console.WriteLine("repetitive key " + id + " in " + container.GetType().Name);
                                    Console.Read();
                                    return;
                                }
                                dict.GetType().GetMethod("Add").Invoke(dict, new Object[] { id, t });
                            }
                
                            IFormatter f = new BinaryFormatter();
                            Stream s = new FileStream(path + temp.Name + ".bytes", FileMode.OpenOrCreate,
                                      FileAccess.Write, FileShare.Write);
                            f.Serialize(s, container);
                            s.Close();
                        }
                 
                 
                   CreateDataManager(assembly);
                   
                   
                  • 1

                  最后这里还创建了一个DataManager用于管理之前导出的数据.这也是Unity中获取数据的接口

                          //创建数据管理器脚本
                          private static void CreateDataManager(Assembly assembly)
                          {
                              IEnumerable types = assembly.GetTypes().Where(t => { return t.Name.Contains("Container"); });
                  
                              StringBuilder source = new StringBuilder();
                              source.Append("/*Auto create\n");
                              source.Append("Don't Edit it*/\n");
                              source.Append("\n");
                  
                              source.Append("using System;\n");
                              source.Append("using UnityEngine;\n");
                              source.Append("using System.Runtime.Serialization;\n");
                              source.Append("using System.Runtime.Serialization.Formatters.Binary;\n");
                              source.Append("using System.IO;\n\n");
                              source.Append("[Serializable]\n");
                              source.Append("public class DataManager : SingletonTemplate<DataManager>\n");
                              source.Append("{\n");
                  
                              //定义变量
                              foreach (Type t in types)
                              {
                                  source.Append("\tpublic " + t.Name + " " + t.Name.Remove(0, 2) + ";\n");
                              }
                              source.Append("\n");
                  
                              //定义方法
                              foreach (Type t in types)
                              {
                                  string typeName = t.Name.Remove(t.Name.IndexOf("Container"));
                                  string funcName = t.Name.Remove(0, 2);
                                  funcName = funcName.Substring(0, 1).ToUpper() + funcName.Substring(1);
                                  funcName = funcName.Remove(funcName.IndexOf("Container"));
                                  source.Append("\tpublic " + typeName + " Get" + funcName + "(int id)\n");
                                  source.Append("\t{\n");
                                  source.Append("\t\t" + typeName + " t = null;\n");
                                  source.Append("\t\t" + t.Name.Remove(0, 2) + ".Dict.TryGetValue(id, out t);\n");
                                  source.Append("\t\tif (t == null) Debug.LogError(" + '"' + "can't find the id " + '"' + " + id " + "+ " + '"' + " in " + t.Name + '"' + ");\n");
                                  source.Append("\t\treturn t;\n");
                                  source.Append("\t}\n");
                              }
                  
                              加载所有配置表
                              source.Append("\tpublic void LoadAll()\n");
                              source.Append("\t{\n");
                              foreach (Type t in types)
                              {
                                  string typeName = t.Name.Remove(t.Name.IndexOf("Container"));
                                  source.Append("\t\t" + t.Name.Remove(0, 2) + " = Load(" + '"' + typeName + '"' + ") as " + t.Name + ";\n");
                              }
                              source.Append("\t}\n\n");
                  
                              //反序列化
                              source.Append("\tprivate System.Object Load(string name)\n");
                              source.Append("\t{\n");
                              source.Append("\t\tIFormatter f = new BinaryFormatter();\n");
                              source.Append("\t\tTextAsset text = Resources.Load<TextAsset>(" + '"' + "ConfigBin/" + '"' + " + name);\n");
                              source.Append("\t\tStream s = new MemoryStream(text.bytes);\n");
                              source.Append("\t\tSystem.Object obj = f.Deserialize(s);\n");
                              source.Append("\t\ts.Close();\n");
                              source.Append("\t\treturn obj;\n");
                              source.Append("\t}\n");
                  
                              source.Append("}\n");
                              //保存脚本
                              string path = _rootPath + _scriptPath;
                              if (!Directory.Exists(path)) Directory.CreateDirectory(path);
                              StreamWriter sw = new StreamWriter(path + "DataManager.cs");
                              sw.WriteLine(source.ToString());
                              sw.Close();
                          }
                   
                   

                    经过动态编译过后.会输出一个库,这个库里面有所有配置表的类型.我们只需要在Unity中引用这个库就行了.
                    实际测试,在PC端,Android,IOS都是可以使用的.


                    这里写图片描述

                    生成的类就是这样的.
                    这里写图片描述

                    DataManager也是自动生成的.在游戏进入时调用一下LoadAll函数加载数据.后面直接调用对应函数, 根据id就可以取得数据了.

                    评论
                    添加红包

                    请填写红包祝福语或标题

                    红包个数最小为10个

                    红包金额最低5元

                    当前余额3.43前往充值 >
                    需支付:10.00
                    成就一亿技术人!
                    领取后你会自动成为博主和红包主的粉丝 规则
                    hope_wisdom
                    发出的红包
                    实付
                    使用余额支付
                    点击重新获取
                    扫码支付
                    钱包余额 0

                    抵扣说明:

                    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                    余额充值