Unity 制作蓝图工具 C#中的技巧

最近花了两周时间,终于完成一个很简单的蓝图工具。

其中我觉得比较复杂的,就是数据转换和数据计算,因为蓝图中数据类型是没办法直接获取到的。

计算的时候不可能罗列所有类型的相互计算,因为类型大多了。

由于数据要序列化,所以要保存类中的字段是可在Unity可序列化的,例如:Type 和 MethodInfo 以及 object 就不行

object不能序列化就导致反射函数的参数需要转换,带来很多麻烦

一、定义保存数据的类

 [Serializable]
    public class SerializableClass
    {
        public byte bytedata;
        public short shortdata;
        public int intdata;
        public string stringdata = "";
        public float floatdata;
        public double doubledata;
        public bool booldata;

        public Color colordata;
        public Vector2 Vector2data;
        public Vector3 Vector3data;
        public Vector4 Vector4data;
        public Vector2Int Vector2Intdata;
        public Vector3Int Vector3Intdata;
        public AnimationCurve animationCurvedata = new AnimationCurve();
        public Gradient gradientdata = new Gradient();
        public Ease animationType = Ease.Linear;

        public UnityEngine.Object data = null;


        public CustomType type = CustomType.Int16;

        public SerializableClass()
        {

        }

        public SerializableClass(object value)
        {
            type = (CustomType)Enum.Parse(typeof(CustomType), value.GetType().Name);
            FieldInfo[] fieldinfo = typeof(SerializableClass).GetFields();
            
            for (int i = 0; i < fieldinfo.Length; i++)
            {
                if(fieldinfo[i].FieldType == value.GetType() || value.GetType().IsInstanceOfType(fieldinfo[i].FieldType))
                {
                    fieldinfo[i].SetValue(this, value);
                }
            }
            
        }

        public object GetData()
        {
            FieldInfo[] fieldinfo = typeof(SerializableClass).GetFields();

            for (int i = 0; i < fieldinfo.Length; i++)
            {
                if (fieldinfo[i].FieldType.Name == type.ToString()) 
                {
                    return fieldinfo[i].GetValue(this);
                }
            }
            return data;
        }

        public void SetValue(object value )
        {
            if (value == null) return;
            if (typeof(UnityEngine.Object).IsAssignableFrom(value.GetType()))
                type = (CustomType)Enum.Parse(typeof(CustomType), typeof(UnityEngine.Object).Name);
            else
                type = (CustomType)Enum.Parse(typeof(CustomType), value.GetType().Name);
            FieldInfo[] fieldinfo = typeof(SerializableClass).GetFields();

            for (int i = 0; i < fieldinfo.Length; i++)
            {
                if (fieldinfo[i].FieldType == value.GetType() || fieldinfo[i].FieldType.IsAssignableFrom(value.GetType()))
                {
                    fieldinfo[i].SetValue(this, value);
                }
            }
        }

    }

    public enum CustomType
    {
        Byte,
        Int16,
        Int32,
        String,
        Single,
        Double,
        Boolean,
        Color,
        Vector2,
        Vector3,
        Vector4,
        Vector2Int,
        Vector3Int,
        AnimationCurve,
        Gradient,
        Object,
        Ease
    }

首先把数据分成3种,一种是基础的数据类型例如int、float等,第二种是在Unity种定义的没有继承Object的类型,一般是Unity种的结构体,第三种是Unity中定义的继承与Object的类型,例如继承MonoBehaviour的类。

因为C#中的Type不能序列化,所以自定义CustomType,这个通过对比类型名和CustomType中的字段,区分属于哪种类型。

二、计算

 public enum MathParameterEnum
    {
        [CanUsedType(null, "GreaterThan")]
        [TipsName("大于")]
        Greater = 0,
        [CanUsedType(null, "LessThan")]
        [TipsName("小于")]
        Less,
        [CanUsedType(null, "")]
        [TipsName("等于")]
        Equal,
        [CanUsedType(null, "")]
        [TipsName("不等于")]
        NotEqual,
        [CanUsedType(null, "Add")]
        [TipsName("加法")]
        Addition,
        [CanUsedType(null, "Subtract")]
        [TipsName("减法")]
        Subtraction,
        [CanUsedType(null, "Multiply")]
        [TipsName("乘法")]
        Multiply,
        [CanUsedType(null, "Divide")]
        [TipsName("除法")]
        Division,

        [TipsName("绝对值")]
        [CanUsedType(typeof(Mathf) , "Abs")]
        Abs = 8,
        [TipsName("正切")]
        [CanUsedType(typeof(Mathf), "Tan")]
        Tan,
        [TipsName("余弦")]
        [CanUsedType(typeof(Mathf), "Cos")]
        Cos,
        [TipsName("正弦")]
        [CanUsedType(typeof(Mathf), "Sin")]
        Sin,


        [TipsName("长度归一")]
        [CanUsedType(null , "Normalize")]
        Normalized = 12,
        [TipsName("角度" )]
        [CanUsedType(null, "Angle")]
        Angle,
        [TipsName("距离")]
        [CanUsedType(null, "Distance")]
        Distance,
    }

首先通过自定义特性来简化计算的遍历。这里有三种计算类型,一种是基础类型的加减乘除运算,一种是Mathf类或者其他计算类中的函数,还有一种是数据类型自定义的计算。

其中大部分运算都可以通过反射实现,但是这里有两个问题。

1、隐式转换

例如Vector3 可以和float 相乘,也可以和Int相乘,但是Vector3的自定义运算中并没有和int相乘的函数。因为int可以隐式转换乘float,然后再调用float的运算重载。

因此在计算判断的时候,需要考虑类型能否隐式转换为可计算的类型。

 //判断类型是否有隐式转换
    public static bool HasImplicitConversion(Type baseType, Type targetType)
    {
        if (IsNumber(baseType, targetType))
        {
            if(typeof(double) == targetType && (typeof(long) == targetType || typeof(float) == baseType || typeof(int) == baseType|| typeof(short) == baseType|| typeof(byte) == baseType))
            {
                return true;
            }
            if (typeof(float) == targetType && (typeof(long) == targetType || typeof(int) == baseType || typeof(short) == baseType || typeof(byte) == baseType))
            {
                return true;
            }
            if (typeof(long) == targetType && (typeof(int) == baseType || typeof(short) == baseType || typeof(byte) == baseType))
            {
                return true;
            }
            if (typeof(int) == targetType && (typeof(short) == baseType || typeof(byte) == baseType))
            {
                return true;
            }
            if (typeof(short) == targetType && (typeof(byte) == baseType))
            {
                return true;
            }
        }


        return baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
            .Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
            .Any(mi => {
                ParameterInfo pi = mi.GetParameters().FirstOrDefault();
                return pi != null && pi.ParameterType == baseType;
            });
    }

因为C#的基础数据类型,并没有靠op_Implicit去写隐式转换,所以只能手写。

2.基本数据的计算

Vector3这种类型的加减乘除可以通过发射寻找计算函数,但是float,int,double等的加减运算就不行了。

public static BinaryExpression CanMath(ParameterExpression type1, ParameterExpression type2, string methodName)
    {
        try
        {
            BinaryExpression obj = (BinaryExpression)typeof(Expression).InvokeMember(methodName,
 System.Reflection.BindingFlags.InvokeMethod | System.Reflection.BindingFlags.Static
 | System.Reflection.BindingFlags.Public, null, null,
 new object[] { type1, type2 });
            return obj;
        }
        catch (Exception e)
        {
            return null;
        }
    }
  public static object MathNumber(List<object> allobj, MathParameterEnum mathtype)
    {
        Type baseType = null;
        for (int i = 0; i < allobj.Count; i++)
        {
            Type nowtype = allobj[i].GetType();
            if (baseType == null)
                baseType = nowtype;
            else
            {
                if (nowtype != baseType)
                {
                    if (!HasImplicitConversion(nowtype, baseType))
                    {
                        if (HasImplicitConversion(baseType, nowtype))
                        {
                            baseType = nowtype;
                        }
                        else
                            return null;
                    }
                }
            }
        }


        CanUsedType obsAttr = GetEnumMathTip(mathtype.GetType().GetField(mathtype.ToString()));

        ParameterExpression _ParaA = Expression.Parameter(baseType, "a");
        ParameterExpression _ParaB = Expression.Parameter(baseType, "b");
        BinaryExpression _BinaAdd = CanMath(_ParaA, _ParaB, obsAttr.methodName);

        if (_BinaAdd == null) return null;

        Expression<Func<double, double, double>> doubleLamb;
        Expression<Func<float, float, float>> floatLamb;
        Expression<Func<long, long, long>> longLamb;
        Expression<Func<int, int, int>> intLamb;
        Expression<Func<short, short, short>> shortLamb;


        if (baseType == typeof(double))
        {
            doubleLamb = Expression.Lambda<Func<double, double, double>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });
            double data = Convert.ToDouble(allobj[0]);
            for (int i = 1; i < allobj.Count; i++)
            {
                data += doubleLamb.Compile()(data, Convert.ToDouble(allobj[i]));
            }
            return data;
        }
        else if (baseType == typeof(float))
        {
            floatLamb = Expression.Lambda<Func<float, float, float>>(_BinaAdd , new ParameterExpression[] { _ParaA, _ParaB });
            float data = Convert.ToSingle(allobj[0]);
            for (int i = 1; i < allobj.Count; i ++)
            {
                data = floatLamb.Compile()(data , Convert.ToSingle(allobj[i]));
            }
            return data;
        }
        else if (baseType == typeof(long))
        {
            longLamb = Expression.Lambda<Func<long, long, long>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });
            long data = Convert.ToInt64(allobj[0]);
            for (int i = 1; i < allobj.Count; i++)
            {
                data = longLamb.Compile()(data, Convert.ToInt64(allobj[i]));
            }
            return data;
        }
        else if (baseType == typeof(int))
        {
            intLamb = Expression.Lambda<Func<int, int, int>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });
            int data = Convert.ToInt32(allobj[0]);
            for (int i = 1; i < allobj.Count; i++)
            {
                data = intLamb.Compile()(data, Convert.ToInt32(allobj[i]));
            }
            return data;
        }
        else if (baseType == typeof(short))
        {
            shortLamb = Expression.Lambda<Func<short, short, short>>(_BinaAdd, new ParameterExpression[] { _ParaA, _ParaB });
            short data = Convert.ToInt16(allobj[0]);
            for (int i = 1; i < allobj.Count; i++)
            {
                data = shortLamb.Compile()(data, Convert.ToInt16(allobj[i]));
            }
            return data;
        }

        return null;
    }

这种方式虽然还是很麻烦,但是比用if else 简单了很多很多。

 

(T)Convert.ChangeType(data.Value, typeof(T));  应用为类型转换为范型

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页