之前就在做的一个 unity 中使用的 Excel 导出工具,继续完善了一下。
这次改了挺多内容的,一方面是使用了反射然后再优化了代码,属性拓展更加简单,另一方面是优化了模板处理和窗口显示。相关的代码以及Demo已经打包为Unity包,连接如下。上一个版本有比较详细的使用方法介绍,链接也放在下面可供参考
链接:https://pan.baidu.com/s/1AdsaUDOW4e4D-beWUaqhXA?pwd=wsad
提取码:wsad
前一个版本的文章:https://blog.csdn.net/Blue_carrot_/article/details/130954127
~ 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);
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";
}
}
那么对于一个属性而言PropertyInfo,可以抽象如下
internal class PropertyInfo
{
private string typeName
public bool Check(string str)
public void 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);
}
}
}
那在此之后,我们去拓展一个属性,只需要创建一个对应的委托即可!
比如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;
}
};
代码模板独立成一个文本Template_code.txt,这样比起在UnIty中会更容易更改,另外重新更改了替换方式,尽量将能改的内容都写在样本中。
模板最常用的就是替换啦,这里我们可以设定“ {sheetName}“ 和“{fileName}”这两个字符,这样可以用于替换文件名和表格名。
对于一个属性,要替换的内容有类型,名字之类的,但因为属性数量不同,所以单纯的替换就比较麻烦,这里加入属性段的概念,[properties][propertiesEnd]用于标记属性段,此时标记的部分就会按照属性来依次重复,然后对应的去替换属性的相关值
那么,我们约定模板内的特殊符号以及其作用。
内容 | 作用 |
---|---|
{fileName} | 替换文件名 |
{sheetName} | 替换表格名 |
{parse} | 替换属性转化方式 |
{type} | 替换属性类型 |
{name} | 替换属性名字 |
{note} | 替换属性注释 |
{setting} | 替换属性设置 |
[properties] | 标记属性段的开始 |
[properties] | 标记属性段的结束 |
估计单讲没什么感觉,直接看模板估计就知道这么设定的作用了。
using System.Collections.Generic;
using UnityEngine;
using Exporter;
namespace GDT
{
///
/// {sheetName} data,{fileName}.xlsx
///
public class DR{sheetName}: IDataRow
{
[properties]
///
/// {note} {setting}
///
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();
}
}
}
主要是之前只弹出提示窗口显示有限,而且显示不完,所以特地额外弄了个窗口来显示这个内容。然后加了点富文本,方便显示。具体就不说怎么弄了,项目里面都有。关于Editor相关的内容以后再写文章记录一下吧。现在就简单放一下图吧。
这个处理的主要内容就讲完咯,下回有空再见吧。以后的可能会完善的内容有,关联额外检查、公式导出、Json导出等。不过等用到了再去弄吧,做这个东西还挺花时间的。然后,使用上有问题的话,可以再Q我.