Unity C#配置表工具

一般在游戏开发中策划都会把数据配置在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);

最后这里还创建了一个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就可以取得数据了.

  • 8
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值