[Programming tools] Unity table export tool TableExporter1.1

0. Preface

The Excel export tool used in unity that I was working on before continued to improve.
A lot of content has been changed this time. On the one hand, it uses reflection and then optimizes the code, making attribute expansion easier. On the other hand, it optimizes template processing and window display. Relevant codes and demos have been packaged as Unity packages, the links are as follows. The previous version has a more detailed introduction to how to use it, and the link is also placed below for reference

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

1. Attribute expansion and optimization

(1) Reflection acquisition conversion function TryParse

~ TryParse,
such as "int.TryParse", is used to try to convert a string into an int type. This is still very important for data export tools. It can be used for data verification, and it can also be used as an int when the game is running. Convert the data to the corresponding type.

~ Get TryParse
as a unityEditor related plug-in. Unlike other independent export tools, you can directly get the classes being used in the program through reflection. In this case, we can use reflection to obtain the conversion function TryParse of the type written in the table, and use this function to verify the data and process the data after export.

For example, for the "Color" type, we need to have a method to verify whether the data is correct, and to write the conversion method when exporting the code. ColorUtility.TryParseHtmlString has been provided in Unity to convert hexadecimal colors, such as "#FF00FF". We can enumerate the functions corresponding to each attribute into a class, which is convenient for us to call reflectively. as follows:

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;
    }
}

The delegate is used here. Don’t worry about the extra overhead of the delegate. The delegate will have a little extra overhead when it is created, but it is almost the same as the original function when executed. Compared with reflecting different functions in different classes, using delegates does not need to consider assembly issues, which is obviously much more convenient. Then the reflection acquisition function can be handled like this.

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

~ When using TryParse
at this time, because the commission of TryParse has been obtained, then just call Invoke to use it.

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

(2) Reflection to obtain EmptyReplace

EmptyReplace is actually just for when this form is not configured, I expect to have a default value, such as bool type, and I hope it can be "False" when no value is filled. Basic reflection acquisition is also the same as above. Just put the definition here

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) Attribute type

Then for a property, PropertyInfo can be abstracted as follows

 internal class PropertyInfo
 {
    
    
  		private string typeName
  	 	public bool Check(string str)
  	 	public void ReplaceParse()
 }
  • typeName: attribute type name, which type can be distinguished in the table
  • Check: type verification method, used to check whether the data is correct
  • ReplaceParse: This is mainly to generate code for processing, telling the operation how to convert data into an attribute

These two methods can basically be solved when TryPrase is obtained. I won't go into details, the complete code is as follows for reference.

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) Attribute expansion

After that, we go to expand a property, just need to create a corresponding delegate!

For example, Vector2Int, we only need to write the conversion function in DataTable. (Here it is necessary to prepend a V, such as "V1,1000", mainly to distinguish numbers from strings. If there is no prepending character, Excel will consider it as a number in normal times, such as "1,1000", and it will be It is recognized as the number 11000, in order to prevent mistakes at that time, so a V is added)

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 processing

The code template is independent into a text Template_code.txt, which is easier to change than in Unity. In addition, the replacement method has been changed again, and all the content that can be changed should be written in the sample as much as possible.

(1) Replacement content

The most commonly used template is replacement. Here we can set the two characters "{sheetName}" and "{fileName}", which can be used to replace the file name and table name.

(2) Attribute segment

For a property, the content to be replaced includes type, name, etc., but because the number of properties is different, simple replacement is more troublesome. Here, the concept of property segment is added, [properties][propertiesEnd] is used to mark the property segment, here The marked part will be repeated in sequence according to the attribute, and then the corresponding value of the attribute will be replaced

(3) Template special symbol definition

Then, we agree on the special symbols in the template and their functions.

content effect
{fileName} replace filename
{sheetName} replace table name
{parse} Replace attribute conversion method
{type} Replace property type
{name} replace attribute name
{note} replace attribute annotations
{setting} Override property settings
[properties] Marks the start of an attribute section
[properties] Marks the end of the attribute section

(4) template

It is estimated that there is no sense in just talking about it. If you look at the template directly, you will know the effect of such a setting.

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. Panel optimization

The main reason is that the prompt window that popped up was limited and could not be displayed before, so an additional window was specially created to display this content. Then add some rich text for easy display. I won’t talk about how to do it specifically, it’s all in the project. I will write an article about the content related to the Editor later. Just put a picture now.

insert image description here

4. It's over

The main content of this processing is finished, see you next time when you have time. The content that may be improved in the future includes additional inspection of associations, formula export, Json export, etc. But let's get it when you need it, it takes time to make this thing. Then, if you have any problems in use, you can ask me again.

Guess you like

Origin blog.csdn.net/Blue_carrot_/article/details/131169249
Recommended