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

news/2024/7/21 4:16:22 标签: unity, excel, c#

[ 目录 ]

  • 0. 前言
  • 1. 属性拓展优化
    • (1)反射获取转化函数 TryParse
    • (2)反射获取EmptyReplace
    • (3)属性类型
    • (4)属性拓展
  • 2. 模板处理
    • (1)替换内容
    • (2)属性段
    • (3)模板特殊符号定义
    • (4)模板
  • 3. 面板优化
  • 4. 结束咯

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我.


http://www.niftyadmin.cn/n/434621.html

相关文章

YOLOv5改进系列(9)——替换主干网络之EfficientNetv2

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

【夜深人静学数据结构与算法 | 第二篇】后缀(逆波兰)表达式

目录 前言&#xff1a; 中缀表达式&#xff1a; 后缀表达式&#xff1a; 中缀表达式转后缀表达式&#xff1a; 后缀表达式计算结果&#xff1a; 总结&#xff1a; 前言&#xff1a; 计算机在计算四则运算的时候&#xff0c;由于括号以及运算优先级的存在&#xff0c;并不…

chatgpt赋能python:Python怎么断行-让代码更易读

Python怎么断行 - 让代码更易读 大多数Python程序员都知道&#xff0c;代码可读性非常重要。好的代码应该易于阅读和理解&#xff0c;而不是让人困惑和痛苦。 然而&#xff0c;我们经常会发现一些Python代码在一行中拥挤着多个表达式、长变量名混杂其中&#xff0c;让人感到相…

【Thunder送书 | 第三期 】「Python系列丛书」

文章目录 前言《Python高效编程——基于Rust语言》《Python从入门到精通》《Python Web深度学习》《Python分布式机器学习》文末福利 | 赠书活动 前言 Thunder送书第三期开始啦&#xff01;前面两期都是以【文末送书】的形式开展&#xff0c;本期将赠送Python系列丛书&#xff…

基于Java校园驿站管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

rgba()中使用变量作为参数

系列文章目录 文章目录 系列文章目录前言一、这是我的Javascript代码二、使用步骤1.引入库2.解决 总结 前言 rgba()是一种CSS颜色函数&#xff0c;用于设置颜色的红、绿、蓝和透明度值。它由四个参数组成&#xff0c;分别是红色&#xff08;R&#xff09;、绿色&#xff08;G&…

TS系列之keyof详解,示例

文章目录 前言一、keyof是什么总结 前言 如果你用过TS的工具类型&#xff0c;Partial、Required、Pick、Record。那么你可能看过他们内部实现都有共同点就是keyof关键字。即使没有见过&#xff0c;那么下面就一起来了解一下&#xff0c;keyof关键字的详细作用吧。 一、keyof是…

一分钟学一个 Linux 命令 - find 和 grep

前言 大家好&#xff0c;我是 god23bin。欢迎来到《一分钟学一个 Linux 命令》系列&#xff0c;每天只需一分钟&#xff0c;记住一个 Linux 命令不成问题。今天需要你花两分钟时间来学习下&#xff0c;因为今天要介绍的是两个常用的搜索命令&#xff1a;find 和 grep 命令。 …