一、前言
在上一篇中,已经可以处理基本的数据类型的动态编辑栏创建并且保存数据的功能。但是,对于自定义的类型,比如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这样的自定义类型,需要额外的脚本对其进行处理,控制这个组合编辑框
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[i]);
}
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
public void Init(Type variableType, string name,object value,string[] enumValue=null,Type strcutType=null,bool isInteractable=true)
{
...
}
2、动态生成
还需要用到之前定义的方法“GetVaule_ReflectMethod”,该方法处理了结构体的泛型和非泛型的字段或属性,并且将得到的字段或属性的
/// <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”是用来封装字段或属性的名字、值和类型的,其定义为:
/// <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"为:
[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里的所有字段的编辑框
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[i].VariableType, tempListReflectInfo[i].VariableName, tempListReflectInfo[i].VariableValue, tempStrDropdwonList, testData.GetType(), isInteractable);
listSubObjEA.Add(tempSubObjEA);
}
}
}
3、反射赋值
在动态的创建了结构体的字段编辑框之后,修改编辑框里面的任何值,点击保存就可以将修改后的值动态的赋值给生成动态编辑栏的变量”testData",保存按钮的代码为:
public void BtnSave_OnClick()
{
for (int i = 0; i < listSubObjEA.Count; i++)
{
testData = (TestData)SetValue_ReflectMethod(testData, listSubObjEA[i].M_VaribleName, listSubObjEA[i].M_Vaule);
}
}
以及用到的反射赋值的方法“SetValue_ReflectMethod”为:这个方法对比之前的稍微进行了一些修改,将“paramName”的参数类型由
/// <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类型,第二个是变量的类型。
4、完整的三个脚本的代码为:
控制脚本GlobalCtr:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;
using UnityEngine;
public class GlobalCtr : MonoBehaviour
{
[SerializeField] UI2D_SubObjEditorAttr prefabSubObjEA;
public TestData testData;
public Transform subObjEAParent;
private List<UI2D_SubObjEditorAttr> listSubObjEA = new List<UI2D_SubObjEditorAttr>();
// Use this for initialization
void Start()
{
// Vector3 tempVec = Vector3.one*10;
// testData =(TestData) SetValue_ReflectMethod(testData, "Pos", tempVec);
}
// Update is called once per frame
void Update()
{
}
public void BtnSave_OnClick()
{
for (int i = 0; i < listSubObjEA.Count; i++)
{
testData = (TestData)SetValue_ReflectMethod(testData, listSubObjEA[i].M_VaribleName, listSubObjEA[i].M_Vaule);
}
}
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[i].VariableType, tempListReflectInfo[i].VariableName, tempListReflectInfo[i].VariableValue, tempStrDropdwonList, testData.GetType(), isInteractable);
listSubObjEA.Add(tempSubObjEA);
}
}
}
/// <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;
}
/// <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;
}
}
[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;
}
}
/// <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;
}
}
动态编辑栏脚本:UI2D_SubObjEditorAttr
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UI2D_SubObjEditorAttr : MonoBehaviour
{
public delegate bool Editor_Event(string name, string value);
/// <summary>
/// 单个编辑输入栏结束编辑事件
/// </summary>
public event Editor_Event OnEndEditor_Event;
public string M_ShowName
{
get
{
return textName.text;
}
}
public string M_VaribleName
{
get
{
return varibleName;
}
}
public object M_Vaule
{
get
{
object tempValue="";
if (valueDropdown.gameObject.activeSelf)
{
tempValue = valueDropdown.value.ToString();
}
else if(valueInputContent.gameObject.activeSelf)
{
tempValue = valueInputContent.text;
}
else if(valueToggle.gameObject.activeSelf)
{
tempValue = valueToggle.isOn.ToString();
}
else if(valueVectorInput.gameObject.activeSelf)
{
tempValue = valueVectorInput.M_Value;
}
return tempValue;
}
}
[SerializeField]
public Text textName;
[HideInInspector]
public InputField valueInputContent;
[HideInInspector]
public Toggle valueToggle;
[HideInInspector]
public Dropdown valueDropdown;
private string lastValue;
private string curValue;
private bool isInitSucced = false;
/// <summary>
/// 此属性或字段条原本的变量名
/// </summary>
private string varibleName;
private UI2D_SubObjEAVectorInput valueVectorInput;
// Use this for initialization
void Start()
{
// Debug.Log(gameObject.name);
}
// Update is called once per frame
void Update()
{
if (!isInitSucced)
{
return;
}
}
public void UIOnEndEditor_Event()
{
if (null != OnEndEditor_Event)
{
Debug.Log("不为空");
curValue = valueInputContent.text;
if (!OnEndEditor_Event(textName.text, curValue))
{
valueInputContent.text = lastValue;
}
else
{
valueInputContent.text = curValue;
lastValue = curValue;
}
}
}
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[i]);
}
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控制脚本:UI2D_SubObjEAVectorInput
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UI2D_SubObjEAVectorInput : MonoBehaviour
{
public Vector3 M_Value
{
get
{
Vector3 tempV = new Vector3();
tempV.x = float.Parse(inputX.text);
tempV.y = float.Parse(inputY.text);
tempV.z = float.Parse(inputZ.text);
return tempV;
}
}
[SerializeField] InputField inputX;
[SerializeField] InputField inputY;
[SerializeField] InputField inputZ;
private bool isInitSucced = false;
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (!isInitSucced)
{
return;
}
}
public void Init(string value)
{
string[] tempStrV = value.Split(',');
if (null == tempStrV)
{
Debug.Log("Vector3数据错误!");
return;
}
inputX.text = tempStrV[0];
inputY.text = tempStrV[1];
inputZ.text = tempStrV[2];
isInitSucced = true;
}
}
三、总结
1、处理了不同类型输入框的不同,并且输入框内容的控制
2、处理了自定义类型的输入框动态生成,并且将其生成的输入框内容反射赋值给原结构体实例
3、不足之处,还需要拓展针对更多的数据类型或自定义组合类型
4、不能动态的进行界面排布,生成的都是依次往下排