在Unity游戏开发中,如何把配置表的EXCEl文档转换为Xml,binary等


行文背景

近期在学习一个AB包生成和加载框架,里面有一部分关于配置表的课程。于是把这部分单独拿出来,并且进行了一定的修改,以巩固学习到的知识。这里记录一下框架中用到的知识(相关类以及用法)。

C#中的反射

1.基本释义

反射提供描述程序集、模块和类型的对象(Type 类型)。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性。 如果代码中使用了特性,可以利用反射来访问它们。 通俗的来讲,我们这里用到的是动态创建一个已有或者未知的类,然后给它的成员进行赋值,或者运行其中的方法成员。以至于我们能够根据数据来生成所想要的类,而不用提前知道这个类是什么。 程序集:System.Reflection.Emit,System.Reflection

2.动态创建类

通常的流程图:

创建动态类的基础流程图

代码示例如下:

public static void CreateType(string className, ref Type classType, ref Type enumType)
{
    //动态创建程序集
    AssemblyName demoName = new AssemblyName("MyTestExcel");
    AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(demoName, AssemblyBuilderAccess.Run);
    //动态创建模块
    ModuleBuilder mb = dynamicAssembly.DefineDynamicModule("MyTestModule");

    //在模块下动态创建类
    TypeBuilder tb = mb.DefineType(className, TypeAttributes.Public);

    //在类下定义一个带整型参数的构造函数将其存储在私有字段中。
    Type[] parameterTypes = { typeof(int) };
    ConstructorBuilder ctor1 = tb.DefineConstructor(MethodAttributes.Public,CallingConventions.Standard,parameterTypes);

    //在类下定义一个叫Property字段的字符串数组(属性)
    PropertyBuilder pbString = tb.DefineProperty("Property字段", PropertyAttributes.HasDefault | PropertyAttributes.None, typeof(string[]),null);

    //在类下定义一个叫Field字段的静态的公有的字符串数组(字段)
    FieldBuilder fbNumber = tb.DefineField("Field字段", typeof(string), FieldAttributes.Public|FieldAttributes.Static);

    在类下定义一个方法(看起来很难,要学MISL中间语言)
    //MethodBuilder mbBuilder  = tb.DefineMethod("Method方法", MethodAttributes.Public | MethodAttributes.Static);
    //ILGenerator mbIL = mbBuilder.GetILGenerator();
    //mbIL.Emit(OpCodes.Ldarg_0);
    //mbIL.Emit(OpCodes.Call);

    //在模块下动态创建一个枚举类
    EnumBuilder eb = mb.DefineEnum("Elevation", TypeAttributes.Public, typeof(int));
    eb.DefineLiteral("Low", 0);
    eb.DefineLiteral("High", 1);

    enumType = eb.CreateTypeInfo();
    classType = tb.CreateType();
}
定义构造函数和方法,使用ILGenerator类进行方法体的创建,这里暂时没看懂/(ㄒoㄒ)/~~。看了下,可能需要研修下MISL是如何运行的。。。方法和字段的特性(私有,公有等)则选择相应的枚举类型进行定义。

3.实例化类并获取成员

Activator,首先使用的是这个类,在System命名空间下,用以在本地或从远程创建对象类型,或获取对现有远程对象的引用。通常我们使用的方法是CreateInstance(Type),使用类型的无参数构造函数创建指定类型的实例。如果有其他需求,可以查看它的重载方法,很多并且够用! 而获取实例化后的成员则是使用Type类下的各种获取成员的方法:

GetEnumNames() :返回当前枚举类型中各个成员的名称
GetEvent(String) :返回表示指定的公共事件的 EventInfo 对象
GetEvents() :返回由当前 Type 声明或继承的所有公共事件
GetField(String) :搜索具有指定名称的公共字段
GetFields() :返回当前 Type 的所有公共字段
GetMember(String) :搜索具有指定名称的公共成员
GetMembers() :返回为当前 Type 的所有公共成员
GetMethod(String) :搜索具有指定名称的公共方法
GetMethods() :返回为当前 Type 的所有公共方法
GetProperty(String) :搜索具有指定名称的公共属性
GetProperties() :返回为当前 Type 的所有公共属性
GetConstructors() :返回为当前 Type 定义的所有公共构造函数
GetEnumName(Object) :返回当前枚举类型中具有指定值的常数的名称
GetConstructor(Type[]) :搜索其参数与指定数组中的类型匹配的公共实例构造函数

方法有很多重载,用于满足不同的需求,基本是使用如下代码:
string[] fieldName = new string[] { "11", "22", "33" };
Type type = null;
Type type1 = null;
CreateType("TestClass", ref type, ref type1);

object obj = Activator.CreateInstance(type);
FieldInfo[] infoes = obj.GetType().GetFields();
foreach(var info in infoes)
{
    if (info.FieldType == typeof(string[]))
    {
        info.SetValue(obj, fieldName);
    }
}
object str = obj.GetType().GetField("Field字段").GetValue(obj);
str = " 我是一个string字段 ";
只是举个简单的例子,有一个比较需要注意的是对于集合类的字段或者属性,使用SetValue的方法进行赋值是比较安全的,而其他的则可以使用直接赋值。例如上诉代码中的str。SetValue在各种Get方法的返回值类中都是带有的。

读写文档中的数据

1.Xml文档

程序的语言中,读取文档数据生成类,叫做反序列化,而反过来叫序列化。在C#中有对应的类来承担xml文档序列化的功能,所以我们很简单就能写出对应的代码。在System.Xml.Serialization程序集下的XmlSerializer类来承担序列化和反序列化的功能。

Deserialize(Stream):反序列化指定 Stream 包含的 XML 文档
Serialize(Stream, Object):使用指定的 Object 序列化指定的 Stream 并将 XML 文档写入文件

通常使用上述两个方法就能达到我们的目的,更多需求可查看重载方法。示例代码如下:
/// <summary>
/// 类序列化成xml
/// </summary>
public static bool XmlSerialize(string path, object obj)
{
    try
    {
        using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
        {
            using (StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8))
            {
                XmlSerializer xs = new XmlSerializer(obj.GetType());
                xs.Serialize(sw, obj);
            }
        }
        return true;
    }
    catch (Exception e)
    {
        Debug.LogError("此类无法转换成xml: " + obj.GetType() + "," + e);
    }
    return false;
}
/// <summary>
/// 反序列化xml文件
/// </summary>
public static T XmlDeserialize<T>(string path) where T : class
{
    T t = default(T);
    try
    {
        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
        {
             XmlSerializer xs = new XmlSerializer(typeof(T));
             t = (T)xs.Deserialize(fs);
        }
        return t;
    }
    catch (Exception e)
    {
        Debug.LogError("Xml文件读取错误:" + path + "," + e);
    }
    return t;
}

2.Binay文档

同上Xml一样,二进制也有相应的类来实现序列化和反序列化的功能,在程序集System.Runtime.Serialization.Formatters.Binary下。代码则和xml类似,把中间的类XmlSerializer换成二进制的BinaryFormatter即可,如下:
/// <summary>
/// 类序列化成二进制
/// </summary>
public static bool BinarySerialize(string path, object obj)
{
    try
    {
        using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
        {
            BinaryFormatter xs = new BinaryFormatter();
            xs.Serialize(fs, obj);
        }
        return true;
    }
    catch (Exception e)
    {
        Debug.LogError("此类无法转换成二进制: " + obj.GetType() + "," + e);
    }

    return false;
}
/// <summary>
/// 反序列化二进制
/// </summary>
public static object BinaryDeserialize(string path)
{
    object obj = null;
    try
    {
        using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
        {
            BinaryFormatter xs = new BinaryFormatter();
            obj = xs.Deserialize(fs);
        }
    }
    catch(Exception e)
    {
        Debug.LogError("二进制文件读取错误: " + path + "," + e);
    }

    return obj;
}

3.Excel文档

对于excel文档,则有很多种选择,出去系统自带的方法,各种眼花缭乱的插件也是数不胜数。这里使用了一个叫Epplus的开源插件,具体的使用方法就不介绍了,主要使用的是它的ExcelPackage类,ExcelWorkSheets类。获取到文档内容之后进行遍历赋值。在读取数据的时候,提前定义SheetWork和SheetData数据类,方便转换类的数据到Excel的形式。
/// <summary>
/// Excel标签页数据类
/// </summary>
public class SheetWork
{
    public List<SheetData> allSheetData = new List<SheetData>();
}

/// <summary>
/// Excel表格数据类
/// </summary>
public class SheetData
{
    public string Name { get; set; }
    public Type Type { get; set; }
    public string Data { get; set; }
}

Excel,Xml和Binary互相转换

1,转换流程

一张简单的图,展示下互相转换是如何实现的:

在这里插入图片描述


2,一些小知识

不管如何转换,都是使用类做中间环节。对于已知的类可以直接实例化进行数据读取和转换,而对于不存在的类,则需要使用第一节介绍的动态创建进行生成相应的类。几个简单的知识点在下面附上代码。 如何在程序集中搜索类:
Type type = null;
//AppDomain,应用程序域,GetAssemblies方法可以获取当前程序域中的程序集(Assembly数组)
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
    type = asm.GetType(name);
    if (type != null) break;
}
获取unity中选中的文件的名字:
[MenuItem("Assets / Excel和Xml / Class转Binary")]
public static void AssetsClassToBinary()
{
	//选中的文件Object数组
    UnityEngine.Object[] objs = Selection.objects;
    for (int i = 0; i < objs.Length; i++)
    {
        //显示进度条
        EditorUtility.DisplayProgressBar("文件下的Class转Binary", "正在扫描" + objs[i].name + "... ...", 1.0f / objs.Length * i);
        ClassToBinary(objs[i].name);
        //Debug.Log("Class转Binary已完成:" + objs[i].name);
        Debug.Log(objs[i].name + "Class转Binary已完成,路径为:" + dataPath + objs[i].name + ".byte");
    }
    //刷新资源管理器
    AssetDatabase.Refresh();
    //关闭进度条
    EditorUtility.ClearProgressBar();
}

写在最后

由于在学习中,很多地方可能不太严谨,大家可以一起学习交流。这个项目也传到了GitHub上,有想要源码的可以去取。项目地址为:https://github.com/IceCream-Eayet/ExcelFramWork。很多不完善的地方,很多新的想法也许还会更新~~ 以上所有代码均来自[siki学院](http://www.sikiedu.com/)的课程教学进行修改,对于一个在线教育网站,很多unity课程让学习变得十分简单。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值