[编程工具]Unity配表导出工具TableExporter1.1

0. 前言

之前就在做的一个 unity 中使用的 Excel 导出工具,继续完善了一下。
这次改了挺多内容的,一方面是使用了反射然后再优化了代码,属性拓展更加简单,另一方面是优化了模板处理和窗口显示。相关的代码以及Demo已经打包为Unity包,连接如下。上一个版本有比较详细的使用方法介绍,链接也放在下面可供参考

链接:https://pan.baidu.com/s/1AdsaUDOW4e4D-beWUaqhXA?pwd=wsad 
提取码:wsad
前一个版本的文章:https://blog.csdn.net/Blue_carrot_/article/details/130954127

1. 属性拓展优化

(1)反射获取转化函数 TryParse

~ TryParse
比如“int.TryParse”,是用于尝试将字符串转化为int类型,这对于数据导出工具来说还是很重要的,可以用于做数据的校验,另外在游戏运行时也可以作为将数据转化为对应类型。

~ 获取TryParse
作为一个unityEditor相关插件,与其他独立的导出工具不同的是,可以直接通过反射获取到程序内正在使用的类。这种情况下,我们可以将写在表格中的类型,用反射来获取到该类型的转化函数TryParse,通过这个函数来校验数据以及导出后的数据处理。

比如,“Color”类型,我们需要从有个方法来校验数据是否正确,另外导出代码时能够写入转化的方法。Unity中已经提供了ColorUtility.TryParseHtmlString来转16进制的颜色,比如“#FF00FF”。我们可以把每个属性对应的函数都列举到一个类中,方便我们去反射调用。如下:

using UnityEngine;
namespace Exporter
{
    
    
    public static partial class DataTable
    {
    
    
    	public delegate bool TryParseFunc<T>(string text, out T result);
    	
        public static TryParseFunc<int> TryParse_int = int.TryParse;
        public static TryParseFunc<float> TryParse_float = float.TryParse;
        public static TryParseFunc<double> TryParse_double = double.TryParse;
        public static TryParseFunc<bool> TryParse_bool = bool.TryParse;
        public static TryParseFunc<Color> TryParse_Color = ColorUtility.TryParseHtmlString;
    }
}

这里用到的是delegate委托,不用担心委托会有额外的开销,委托在新建的时候会有额外一点开销,但执行的时候几乎和原本的函数是一致的。比起反射到各个不同的类中的不同函数,使用委托不用考虑程序集的问题,这样显然会方便很多。那么反射获取函数可以这样处理.

string TryParseDelegateFieldName = "TryParse_" + typeName;
FieldInfo tryParseFuncFieldInfo = typeof(DataTable).GetField(TryParseDelegateFieldName);
Delegate tryParseFunc = tryParseFuncFieldInfo?.GetValue(null) as Delegate;

~ 使用TryParse
这个时候因为已经获取到了TryParse的委托,那么只要调用Invoke去使用就可以了

object[] tryParseParameters = new object[2] {
    
     "", tryParseOutParameter };
(bool)tryParseFunc.Method.Invoke(tryParseFunc.Target, tryParseParameters);

(2)反射获取EmptyReplace

EmptyReplace其实只是为了当这个表格没有配置时,我期望能够有个默认的值,比如说bool类型,希望没有填值的时候能为“False”。基本反射获取也和上边的一样。这里就只放一下定义

using UnityEngine;
namespace Exporter
{
    
    
    public static partial class EmptyReplace
    {
    
    
        public static string Default_int = "0";
        public static string Default_float = "0";
        public static string Default_double = "0";
        public static string Default_bool = "FALSE";
        public static string Default_Color = "#FFFFFF";
    }
}

(3)属性类型

那么对于一个属性而言PropertyInfo,可以抽象如下

 internal class PropertyInfo
 {
    
    
  		private string typeName
  	 	public bool Check(string str)
  	 	public void ReplaceParse()
 }
  • typeName:属性类型名,在表中可区分哪个类型
  • Check:类型的校验方式,用于检查数据是否正确
  • ReplaceParse:这个主要是为了生成代码做处理,告知运行是如何转化数据为是个属性

这个两个方法基本在获取到TryPrase的时候就可以解决了。就不多赘述了,完整的代码如下,可供参考。

using System;
using System.Reflection;
using System.Text;

namespace Exporter
{
    
    
    internal class PropertyInfo
    {
    
    
        private string typeName;
        private bool isStringProperty;
        private Delegate tryParseFunc;
        public string emptyReplace;
        private object tryParseOutParameter;
        private object[] tryParseParameters;
        private string TryParseDelegateFieldName => "TryParse_" + typeName;
        private string EmptyReplaceFieldName => "Default_" + typeName;
        public string EmptyReplace => emptyReplace;

        public bool Init(string typeName)
        {
    
    
            this.typeName = typeName;
            this.isStringProperty = typeName == "string";

            if (isStringProperty)
            {
    
    
                emptyReplace = "";
                return true;
            }
            else
            {
    
    
                FieldInfo tryParseFuncFieldInfo = typeof(DataTable).GetField(TryParseDelegateFieldName);
                tryParseFunc = tryParseFuncFieldInfo?.GetValue(null) as Delegate;

                FieldInfo emptyReplaceInfo = typeof(EmptyReplace).GetField(EmptyReplaceFieldName);
                object value = emptyReplaceInfo?.GetValue(null);
                emptyReplace = value != null ? value as string : "";

                tryParseParameters = new object[2] {
    
     "", tryParseOutParameter };

                return tryParseFunc != null;
            }
        }

        public bool Check(string str)
        {
    
    
            if (isStringProperty)
            {
    
    
                return true;
            }
            else
            {
    
    
                tryParseParameters[0] = str;
                return (bool)tryParseFunc.Method.Invoke(tryParseFunc.Target, tryParseParameters);
            }
        }

        public void ReplaceParse(StringBuilder template, bool isArray, bool isOutputKey, string propertyName)
        {
    
    
            // 获取值
            string getValueFromKey = "DataTable.GetStringFromKey";
            string value = Config.Inst.PropertyParseValue;
            if (isOutputKey)
            {
    
    
                value = getValueFromKey + "(" + value + ")";
            }

            // 获取函数
            string TryParseDelegateName = "DataTable." + TryParseDelegateFieldName;
            string parse = "";
            if (isStringProperty && isArray)
            {
    
    
                // 字符数组
                parse = string.Format("DataTable.SplitStrng({0})", value);
            }
            else if (isStringProperty && !isArray)
            {
    
    
                // 字符
                parse = value;
            }
            else if (!isStringProperty && isArray)
            {
    
    
                // 其他属性数组
                parse = "DataTable.ParseArr<{0}>({1}, {2})";
                parse = string.Format(parse, typeName, value, TryParseDelegateName);
            }
            else if (!isStringProperty && !isArray)
            {
    
    
                // 其他属性
                parse = "DataTable.Parse<{0}>({1}, {2})";
                parse = string.Format(parse, typeName, value, TryParseDelegateName);
            }
            template.Replace("{parse}", parse);
        }
    }
}

(4)属性拓展

那在此之后,我们去拓展一个属性,只需要创建一个对应的委托即可!

比如Vector2Int,我们只需要在DataTable中写入转化函数即可。(这里是需要前置一个V,比如“V1,1000”,主要是为了区别数字与字符串。如果没有前置字符,Excel在常规时会认为他是个数,比如"1,1000",会被识别为数字11000,为了防止到时候失误,所以加了个V)

public static TryParseFunc<Vector2Int> TryParse_Vector2Int = (string text, out Vector2Int result) =>
{
    
    
    if (!text.StartsWith("V"))
    {
    
    
        result = Vector2Int.zero;
        return false;
    }

    string[] temp = text.Substring(1, text.Length - 1).Split(",");
    int x, y;
    if (temp.Length == 2 &&
        int.TryParse(temp[0], out x) &&
        int.TryParse(temp[1], out y))
    {
    
    
        result = new Vector2Int(x, y);
        return true;
    }
    else
    {
    
    
        result = Vector2Int.zero;
        return false;
    }
};

2. 模板处理

代码模板独立成一个文本Template_code.txt,这样比起在UnIty中会更容易更改,另外重新更改了替换方式,尽量将能改的内容都写在样本中。

(1)替换内容

模板最常用的就是替换啦,这里我们可以设定“ {sheetName}“ 和“{fileName}”这两个字符,这样可以用于替换文件名和表格名。

(2)属性段

对于一个属性,要替换的内容有类型,名字之类的,但因为属性数量不同,所以单纯的替换就比较麻烦,这里加入属性段的概念,[properties][propertiesEnd]用于标记属性段,此时标记的部分就会按照属性来依次重复,然后对应的去替换属性的相关值

(3)模板特殊符号定义

那么,我们约定模板内的特殊符号以及其作用。

内容 作用
{fileName} 替换文件名
{sheetName} 替换表格名
{parse} 替换属性转化方式
{type} 替换属性类型
{name} 替换属性名字
{note} 替换属性注释
{setting} 替换属性设置
[properties] 标记属性段的开始
[properties] 标记属性段的结束

(4)模板

估计单讲没什么感觉,直接看模板估计就知道这么设定的作用了。

using System.Collections.Generic;
using UnityEngine;
using Exporter;

namespace GDT
{
    /// <summary>
    /// {sheetName} data,{fileName}.xlsx
    /// </summary>
    public class DR{sheetName}: IDataRow
    {
        
[properties]
        /// <summary>
        /// {note}  {setting}
        /// </summary>
        public {type} {name} { get; protected set; }

[propertiesEnd]
        
        public void ParseDataRow(string input)
        {
            string[] text = input.Split('\t');
            int index = 0;
[properties]
            {name} = {parse};
[propertiesEnd]
        }
        
        private void AvoidJIT()
        {
            new Dictionary<int, DR{sheetName}>();
        }
    }
}

3. 面板优化

主要是之前只弹出提示窗口显示有限,而且显示不完,所以特地额外弄了个窗口来显示这个内容。然后加了点富文本,方便显示。具体就不说怎么弄了,项目里面都有。关于Editor相关的内容以后再写文章记录一下吧。现在就简单放一下图吧。

在这里插入图片描述

4. 结束咯

这个处理的主要内容就讲完咯,下回有空再见吧。以后的可能会完善的内容有,关联额外检查、公式导出、Json导出等。不过等用到了再去弄吧,做这个东西还挺花时间的。然后,使用上有问题的话,可以再Q我.

猜你喜欢

转载自blog.csdn.net/Blue_carrot_/article/details/131169249