找回密码
 注册帐号

扫一扫,访问微社区

zhang273162308 Unity实用小工具或脚本—利用反射制作动态编辑栏(四

53
回复
2868
查看
打印 上一主题 下一主题
[ 复制链接 ]
排名
141
昨日变化

124

主题

595

帖子

7011

积分

Rank: 9Rank: 9Rank: 9

UID
3579
好友
109
蛮牛币
5564
威望
0
注册时间
2013-9-10
在线时间
1559 小时
最后登录
2019-10-12

专栏作家活力之星游戏蛮牛QQ群会员蛮牛哥

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
本帖最后由 zhang273162308 于 2019-5-13 17:41 编辑

一、前言
    在上一篇中,已经可以处理基本的数据类型的动态编辑栏创建并且保存数据的功能。但是,对于自定义的类型,比如Unity自带的Vector类型,还不能处理。另外,编辑栏都是单一的输入框,针对不同的数据创建不同的编辑框,如Bool类型,应该是Toggle类型。本篇主要是对这两个功能的实现。效果图如图所示:点击创建会动态生成结构体里所有的编辑栏,相比之前的只有输入框的编辑栏,这里增加了对Bool、Vector3变量类型的处理,Bool类型的会生成Toggle的,Vector3会生成带X、Y和Z的三个短输入框组合。并且,再Vector3的三个输入框中输入字符会自动删除,只能输入数字。再点击保存的时候会将编辑栏中的数据保存到结构体中,右下角的GlobalCtr中的TestData结构体数据会响应编辑栏的变化。

二、实现
1、动态编辑栏的拓展,在原有的基础上,我们将对原有的动态编辑栏预设新增Toggle、DropdownList和Vector3组合框,如下图所示:每个编辑框都默认是失活的,针对不同的变量类型,显示不同的输入框。

动态编辑栏的初始化函数修改如下:通过Switch里对不同变量的类型进行不同的处理,注意Vector3是Unity自带的类型,它的完整类型名称为:“UnityEngine.Vector3”。针对Vector3这样的自定义类型,需要额外的脚本对其进行处理,控制这个组合编辑框
[AppleScript] 纯文本查看 复制代码
public void Init(Type variableType, string name,string value,string[] enumValue=null,Type strcutType=null,bool isInteractable=true)
    {
        valueInputContent = GetComponentInChildren<InputField>(true);
        valueDropdown = GetComponentInChildren<Dropdown>(true);
        valueToggle = GetComponentInChildren<Toggle>(true);
        valueVectorInput = GetComponentInChildren<UI2D_SubObjEAVectorInput>(true);

        valueDropdown.interactable = isInteractable;
        valueToggle.interactable = isInteractable;
        valueInputContent.interactable = isInteractable;

        varibleName = name;
        textName.text = name;
      //  Debug.Log(variableType.FullName+"v:"+value+"stype:"+strcutType.ToString());
        switch (variableType.FullName)
        {
            case "System.String":
                valueInputContent.text = value;
                curValue = value;
                lastValue = curValue;
                valueInputContent.gameObject.SetActive(true);
                break;
            case "System.Int32":
                if (null == enumValue)
                {
                    valueInputContent.text = value;
                    valueInputContent.gameObject.SetActive(true);
                    valueInputContent.contentType = InputField.ContentType.IntegerNumber;
                }
                else
                {
                    valueDropdown.gameObject.SetActive(true);
                    List<string> tempStrList = new List<string>();
                    for (int i = 0; i < enumValue.Length; i++)
                    {
                        tempStrList.Add(enumValue);
                    }
                    valueDropdown.options.Clear();
                    valueDropdown.AddOptions(tempStrList);
                    valueDropdown.value = int.Parse(value);
                }
                break;
            case "System.Boolean":
                valueToggle.gameObject.SetActive(true);
                bool tempIsOn;
                bool.TryParse(value, out tempIsOn);
                valueToggle.isOn = tempIsOn;
                break;
            case "System.Single":
                valueInputContent.text = value;
                valueInputContent.gameObject.SetActive(true);
                valueInputContent.contentType = InputField.ContentType.DecimalNumber;
                break;

            case "UnityEngine.Vector3":
                valueVectorInput.Init(value);
                valueVectorInput.gameObject.SetActive(true);
                break;
        }
        isInitSucced = true;

    }

Vector3的编辑框控制脚本为:在初始化获取值的时候需要进行额外的处理,Vector3实例转换成字符类型一般为“(xx,xx,xx)"。不同的字段类型会决定输入框的输入字符的类型,上述代码中比如”System.Int32“类型的变量,其输入框的设置为”valueInputContent.contentType = InputField.ContentType.IntegerNumber“,这个设置保证了输入框里只能输入整数数字,并且是可以为负数的,而编号之类的输入框一般都是不为负数的更符合要求,这是Unity自带的功能,不能改变,其实还是有不足之处。

可以通过打印Vector.ToString()来看看。这里其实也可以将object类型作为参数,主要是我不想修改”UI2D_SubObjEditorAttr“脚本里的初始化参数了,比如你可以将Init函数定义成 ,将所有的参数的值都转换成基类object

[AppleScript] 纯文本查看 复制代码
public void Init(Type variableType, string name,object value,string[] enumValue=null,Type strcutType=null,bool isInteractable=true)

{

...

}

2、动态生成
还需要用到之前定义的方法“GetVaule_ReflectMethod”,该方法处理了结构体的泛型和非泛型的字段或属性,并且将得到的字段或属性的
[AppleScript] 纯文本查看 复制代码
 /// <summary>
    /// 获取结构体非泛型的所有属性和字段,并返回由所有属性的名字和值组成的列表,Out 参数返回结构体或类中的泛型属性或字段
    /// (这里只处理类或结构体只有一个List这样的泛型字段或属性)的整个列表
    /// </summary>
    /// <typeparam name="S">结构体类型</typeparam>
    /// <typeparam name="L">结构体属性或字段列表中装载的数据类型</typeparam>
    /// <param name="obj">结构体实例</param>
    /// <param name="listGenericDatas">结构体属性或字段列表</param>
    /// <returns></returns>
    public static List<NP_SingleReflectInfo> GetVaule_ReflectMethod<S, L>(S obj, out List<L> listGenericDatas)
    {
        List<NP_SingleReflectInfo> tempList = new List<NP_SingleReflectInfo>();
        listGenericDatas = new List<L>();
        try
        {
            //遍历所有的属性
            PropertyInfo[] tempPI = obj.GetType().GetProperties();
            foreach (var info in tempPI)
            {
                //装载泛型属性
                if (info.PropertyType.IsGenericType)
                {
                    object tempListObj = info.GetValue(obj, null);
                    listGenericDatas = (List<L>)tempListObj;
                }
                else
                {
                    string tempVarName = info.Name;
                    string tempVarVaule = info.GetValue(obj, null).ToString();
                    //    Debug.Log(info.PropertyType);
                    NP_SingleReflectInfo tempData = new NP_SingleReflectInfo();
                    tempData.Init(info.PropertyType, tempVarName, tempVarVaule);
                    //装载信息到列表中
                    tempList.Add(tempData);
                }
            }
            //遍历所有的字段
            FieldInfo[] tempFI = obj.GetType().GetFields();
            foreach (var itemInfo in tempFI)
            {
                //装载泛型字段
                if (itemInfo.FieldType.IsGenericType)
                {
                    object tempListObj = itemInfo.GetValue(obj);
                    listGenericDatas = (List<L>)tempListObj;
                }
                else
                {
                    string tempVarName = itemInfo.Name;
                    string tempVarVaule = itemInfo.GetValue(obj).ToString();
                    //  Debug.Log(itemInfo.FieldType);
                    NP_SingleReflectInfo tempData = new NP_SingleReflectInfo();
                    tempData.Init(itemInfo.FieldType, tempVarName, tempVarVaule);
                    //装载信息到列表中
                    tempList.Add(tempData);
                }
            }
        }
        catch (Exception e)
        {
            Debug.Log("获取类型数据错误" + e.Message);
            return null;
        }

        return tempList;
    }




名字和值都以String类型返回。结构体“NP_SingleReflectInfo”是用来封装字段或属性的名字、值和类型的,其定义为:


[AppleScript] 纯文本查看 复制代码
/// <summary>
/// 反射获得结构体、类中的单个变量或属性的名字和值
/// </summary>
[Serializable]
public struct NP_SingleReflectInfo
{
    /// <summary>
    /// 变量的类型
    /// </summary>
    public Type VariableType;
    /// <summary>
    /// 变量的名字
    /// </summary>
    public string VariableName;
    /// <summary>
    /// 变量的值
    /// </summary>
    public string VariableValue;
    public void Init(Type type, string name, string value)
    {
        VariableType = type;
        VariableName = name;
        VariableValue = value;
    }
}



定义的结构体“TestData"为:

[AppleScript] 纯文本查看 复制代码
[Serializable]
public struct TestData
{
    public float Number;
    public int Type;
    public string Name;
    public bool IsOPen;
    public Vector3 Pos;
    public void Init(float no,int type,string name,bool isOpen,Vector3 pos)
    {
        Number = no;
        Type = type;
        Name = name;
        IsOPen = isOpen;
        Pos = pos;
    }
}


创建按钮的点击事件方法为:该方法处理的点击创建按钮后,动态的生成结构体TestData变量testdata里的所有字段的编辑框

[AppleScript] 纯文本查看 复制代码
    public void BtnCreate_OnClick()
    {
        List<NP_SingleReflectInfo> tempListReflectInfo = null;
        List<TestData> tempListGeneData;
        //反射创建编辑条信息
        tempListReflectInfo = GetVaule_ReflectMethod(testData, out tempListGeneData);
        if (null != tempListReflectInfo)
        {
            string[] tempStrDropdwonList = null;
            for (int i = 0; i < tempListReflectInfo.Count; i++)
            {
                bool isInteractable = true;
                UI2D_SubObjEditorAttr tempSubObjEA = Instantiate(prefabSubObjEA, subObjEAParent);
                tempSubObjEA.transform.localScale = Vector3.one;
                tempSubObjEA.Init(tempListReflectInfo.VariableType, tempListReflectInfo.VariableName, tempListReflectInfo.VariableValue, tempStrDropdwonList, testData.GetType(), isInteractable);
                listSubObjEA.Add(tempSubObjEA);
            }
        }
    }


3、反射赋值
在动态的创建了结构体的字段编辑框之后,修改编辑框里面的任何值,点击保存就可以将修改后的值动态的赋值给生成动态编辑栏的变量”testData",保存按钮的代码为:


[AppleScript] 纯文本查看 复制代码
    public void BtnSave_OnClick()
    {
        for (int i = 0; i < listSubObjEA.Count; i++)
        {
            testData = (TestData)SetValue_ReflectMethod(testData, listSubObjEA.M_VaribleName, listSubObjEA.M_Vaule);
        }
    }




以及用到的反射赋值的方法“SetValue_ReflectMethod”为:这个方法对比之前的稍微进行了一些修改,将“paramName”的参数类型由


[AppleScript] 纯文本查看 复制代码
    /// <summary>
    /// 使用反射动态设置结构体的变量值
    /// </summary>
    /// <typeparam name="T">结构体类型</typeparam>
    /// <param name="obj">结构体实例</param>
    /// <param name="paramName">变量的名字</param>
    /// <param name="paramValue">变量的值</param>
    /// <returns></returns>
    public static object SetValue_ReflectMethod<T>(T obj, object paramName, object paramValue)
    {
        //先装箱 变成引用类型的
        object tempObj = obj;
        if (obj != null)
        {
            try
            {
                Type tempType = obj.GetType();
                //设置字段
                FieldInfo tempFI = tempType.GetField(paramName.ToString());
                if (null != tempFI)
                {
                     object tempObjValue = Convert.ChangeType(paramValue, tempFI.FieldType);
                    tempFI.SetValue(tempObj, tempObjValue);
                }
                //设置属性
                PropertyInfo tempPI = tempType.GetProperty(paramName.ToString());
                if (null != tempPI)
                {
                    tempPI.SetValue(tempObj, Convert.ChangeType(paramValue, tempPI.PropertyType), null);
                }
            }
            catch (Exception e)
            {
                Debug.Log("编辑错误" + e.Message);
                tempObj = null;
            }
        }
        return tempObj;
    }


原来的“String”类型改为基类”object“类型,因为在将Vector3这样自定义的类型,而非系统基础类型的变量转换的时候不能将字符串进行转换,在” object tempObjValue = Convert.ChangeType(paramValue, tempFI.FieldType);“的时候报错,” Convert.ChangeType()”方法里在处理非基础类型的时候,参数必须是:第一个是该变量值转换成Object类型,第二个是变量的类型。

三、总结
1、处理了不同类型输入框的不同,并且输入框内容的控制
2、处理了自定义类型的输入框动态生成,并且将其生成的输入框内容反射赋值给原结构体实例
3、不足之处,还需要拓展针对更多的数据类型或自定义组合类型
4、不能动态的进行界面排布,生成的都是依次往下排
5、完整的工程下载地址
游客,一分极速PK10您要查看本帖隐藏内容请回复

点评

qiu
报了个错啊The namespace `global::' already contains a definition for `NP_SingleReflectInfo'  发表于 2019-5-29 15:15
怎么没看到下载地址?  发表于 2019-5-10 09:32
回复

使用道具 举报

7日久生情
2350/5000
排名
4093
昨日变化

0

主题

1594

帖子

2350

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蛮牛币
2115
威望
0
注册时间
2017-11-16
在线时间
394 小时
最后登录
2019-10-23
沙发
2019-5-10 08:03:16 只看该作者
666666666666666666666666666666
回复 支持 反对

使用道具 举报

7日久生情
1874/5000
排名
1989
昨日变化

6

主题

565

帖子

1874

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
54335
好友
3
蛮牛币
5443
威望
0
注册时间
2014-11-9
在线时间
621 小时
最后登录
2019-10-22
板凳
2019-5-10 09:00:21 只看该作者
回复

使用道具 举报

7日久生情
1874/5000
排名
1989
昨日变化

6

主题

565

帖子

1874

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
54335
好友
3
蛮牛币
5443
威望
0
注册时间
2014-11-9
在线时间
621 小时
最后登录
2019-10-22
地板
2019-5-10 09:02:19 只看该作者
多谢楼主分享
回复

使用道具 举报

7日久生情
1741/5000
排名
832
昨日变化

2

主题

156

帖子

1741

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
5714
好友
0
蛮牛币
3339
威望
0
注册时间
2013-10-15
在线时间
401 小时
最后登录
2019-10-22
5#
2019-5-10 09:02:47 只看该作者
关注中。。。
回复

使用道具 举报

7日久生情
1874/5000
排名
1989
昨日变化

6

主题

565

帖子

1874

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
54335
好友
3
蛮牛币
5443
威望
0
注册时间
2014-11-9
在线时间
621 小时
最后登录
2019-10-22
6#
2019-5-10 09:03:43 只看该作者
楼主回复了隐藏内容还是没开放
回复 支持 反对

使用道具 举报

4四处流浪
332/500
排名
11373
昨日变化

0

主题

42

帖子

332

积分

Rank: 4

UID
301677
好友
0
蛮牛币
270
威望
0
注册时间
2018-10-29
在线时间
202 小时
最后登录
2019-10-22
7#
2019-5-10 09:08:40 只看该作者

多谢楼主分享
回复

使用道具 举报

7日久生情
3896/5000
排名
1487
昨日变化

0

主题

2150

帖子

3896

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
219676
好友
1
蛮牛币
4438
威望
0
注册时间
2017-7-12
在线时间
892 小时
最后登录
2019-10-23

活力之星

8#
2019-5-10 09:09:00 只看该作者
谢谢分享
回复

使用道具 举报

6蛮牛粉丝
1191/1500
排名
3543
昨日变化

0

主题

331

帖子

1191

积分

Rank: 6Rank: 6Rank: 6

UID
180321
好友
8
蛮牛币
768
威望
0
注册时间
2016-11-21
在线时间
438 小时
最后登录
2019-10-23
9#
2019-5-10 09:16:43 只看该作者
大佬6的很
回复

使用道具 举报

排名
20608
昨日变化

0

主题

15

帖子

90

积分

Rank: 2Rank: 2

UID
218216
好友
0
蛮牛币
71
威望
0
注册时间
2017-4-18
在线时间
49 小时
最后登录
2019-7-29
10#
2019-5-10 09:31:24 只看该作者
666666666666666666666666666666
回复 支持 反对

使用道具 举报

6蛮牛粉丝
1414/1500
排名
2010
昨日变化

21

主题

227

帖子

1414

积分

Rank: 6Rank: 6Rank: 6

UID
44764
好友
0
蛮牛币
765
威望
0
注册时间
2014-9-13
在线时间
488 小时
最后登录
2019-10-23
11#
2019-5-10 10:02:45 只看该作者
6666666666666666666666666666
回复 支持 反对

使用道具 举报

2初来乍到
101/150
排名
31434
昨日变化

0

主题

54

帖子

101

积分

Rank: 2Rank: 2

UID
160425
好友
0
蛮牛币
9
威望
0
注册时间
2016-8-3
在线时间
37 小时
最后登录
2019-7-30
12#
2019-5-10 10:10:29 只看该作者
Unity实用小工具或脚本—利用反射制作动态编辑栏
回复 支持 反对

使用道具 举报

5熟悉之中
845/1000
排名
6128
昨日变化

0

主题

93

帖子

845

积分

Rank: 5Rank: 5

UID
24766
好友
0
蛮牛币
149
威望
0
注册时间
2014-5-12
在线时间
528 小时
最后登录
2019-10-23
13#
2019-5-10 10:15:16 只看该作者
多谢楼主分享
回复

使用道具 举报

5熟悉之中
845/1000
排名
6128
昨日变化

0

主题

93

帖子

845

积分

Rank: 5Rank: 5

UID
24766
好友
0
蛮牛币
149
威望
0
注册时间
2014-5-12
在线时间
528 小时
最后登录
2019-10-23
14#
2019-5-10 10:17:49 只看该作者

多谢楼主分享
回复

使用道具 举报

3偶尔光临
229/300
排名
14745
昨日变化

0

主题

81

帖子

229

积分

Rank: 3Rank: 3Rank: 3

UID
236535
好友
0
蛮牛币
357
威望
0
注册时间
2017-8-8
在线时间
94 小时
最后登录
2019-10-22
15#
2019-5-10 10:22:20 只看该作者
多谢楼主分享
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则