https://zhuanlan.zhihu.com/p/34234315
EditorGUILayout.PropertyField
public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options);
public static bool PropertyField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options);
public static bool PropertyField(SerializedProperty property, bool includeChildren, params GUILayoutOption[] options);
public static bool PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options);
//The scripts below show how to use a propertyField to change your editor. //Attach this first script to the GameObject that you would like to control. Add code in this script for any of the actions you require. using UnityEngine; public class MyGameObjectScript : MonoBehaviour { public int m_MyInt = 75; public Vector3 m_MyVector = new Vector3(20, 1, 0); public GameObject m_MyGameObject; }
//This next script shows how to call upon variables from the "MyGameObject" Script (the first script) to make custom fields in the Inspector for these variables. using UnityEngine; using UnityEditor; // Custom Editor using SerializedProperties. // Automatic handling of multi-object editing, undo, and prefab overrides. [CustomEditor(typeof(MyGameObjectScript))] [CanEditMultipleObjects] public class EditorGUILayoutPropertyField : Editor { SerializedProperty m_IntProp; SerializedProperty m_VectorProp; SerializedProperty m_GameObjectProp; void OnEnable() { // Fetch the objects from the GameObject script to display in the inspector m_IntProp = serializedObject.FindProperty("m_MyInt"); m_VectorProp = serializedObject.FindProperty("m_MyVector"); m_GameObjectProp = serializedObject.FindProperty("m_MyGameObject"); } public override void OnInspectorGUI() { //The variables and GameObject from the MyGameObject script are displayed in the Inspector with appropriate labels EditorGUILayout.PropertyField(m_IntProp, new GUIContent("Int Field"), GUILayout.Height(20)); EditorGUILayout.PropertyField(m_VectorProp, new GUIContent("Vector Object")); EditorGUILayout.PropertyField(m_GameObjectProp, new GUIContent("Game Object")); // Apply changes to the serializedProperty - always do this at the end of OnInspectorGUI. serializedObject.ApplyModifiedProperties(); } }
通常情况是不需要扩展属性面板的,也就是Inspector,但如果需要开发定制的工具和插件,那么就需要扩展属性面板来完成需求。本文会对自定义扩展Inspector进行全面的介绍。
工具类和概念的介绍
自定义Inspector有两个层面,第一个是扩展已有属性的功能,第二个是按照意图增加新的面板功能。扩展已有的功能,包括替换显示文字,动态控制可见性,增加动画,错误提示,数据的校验和限制等等。增加新的功能就是按照需求定制更为复杂的功能。
Unity的内置工具类提供了必要的实现基础,主要有EditorGUILayout和EditorGUI两大类。两者有重叠的部分,也有特有的部分。总体上来说,EditorGUILayout是带有自动布局的,EditorGUI需要传入Rect来对控件进行定位。另外,这两者和GUILayout与GUI概念非常类似,只不过GUI的主要绘制是在场景中(运行时,游戏中可见),EditorGUI上在Inspector中(仅在编辑器中)。
自定义流程
一共分为三步:
- 第一步,继承Editor父类。
- 第二步,添加[CustomEditor(typeof(MonoBehaviour))]注解,告诉编辑器这个类是扩展哪个组件的Inspector。
- 第三步,覆写OnInspectorGUI方法,实现自定义的扩展。
public class CustomEditorTest : MonoBehaviour { [Space(10)] public int intValue; public bool boolValue; public Vector2 v2; public float[] floatArray = new float[] {1.0f, 2.0f, 3.0f}; } [CanEditMultipleObjects, CustomEditor(typeof(CustomEditorTest))] public class CustomEditorTestEditor : Editor { public override void OnInspectorGUI() { // 自定义绘制Inspector } }
自定义属性详细介绍
- 绘制原有属性
public override void OnInspectorGUI() { // 绘制全部原有属性 base.DrawDefaultInspector() // 后面可以扩展自己功能 }
- 自定义绘制
有两个重要的内置对象,target和serializedObject。target代表的是CustomEditorTest本身,而serializedObject代表的是当前Inspector的可绘制对象。
public override void OnInspectorGUI() { // 更新显示 this.serializedObject.Update(); // 自定义绘制 // 应用属性修改 this.serializedObject.ApplyModifiedProperties(); }
- 绘制已经有的属性
有两个接口用来查找已有的属性,FindProperty和FindPropertyRelative。FindProperty用来查找当前属性名对应的属性对象,FindPropertyRelative查找相对于属性对象的属性。
// 显示intValue属性 EditorGUILayout.PropertyField(this.serializedObject.FindProperty("intValue")); // 显示boolValue并替换属性标签为GUIContent EditorGUILayout.PropertyField(this.serializedObject.FindProperty("boolValue"), this.boolValueContent); var v2Property = this.serializedObject.FindProperty("v2"); // 现实v2属性 EditorGUILayout.PropertyField(v2Property); // 设置v2属性的x属性值 v2Property.FindPropertyRelative("x").floatValue = 999.0f; // 分隔符 EditorGUILayout.Separator();
EditorGUILayout.PropertyField能够在Inspector上绘制属性控件,如果返回false表示属性控件不可用,或者处在不可见的状态。
- 绘制数组属性
// 查找floatArray属性 var elements = this.serializedObject.FindProperty("floatArray"); // 属性元素可见,控件展开状态 if (EditorGUILayout.PropertyField(elements)) { // 缩进一级 EditorGUI.indentLevel++; // 设置元素个数 elements.arraySize = EditorGUILayout.DelayedIntField("Size", elements.arraySize); // 绘制元素 for (int i = 0, size = elements.arraySize; i < size; i++) { // 检索属性数组元素 var element = elements.GetArrayElementAtIndex(i); EditorGUILayout.PropertyField(element); } // 重置缩进 EditorGUI.indentLevel--; } // 空格 EditorGUILayout.Space();
对elements.arraySize赋值就会自动设置数组容量。DelayedIntField在回车的时候,控件设置的数值才会返回到arraySize。
- 绘制默认数组和对象
public CustomData data; public CustomData[] datas; [System.Serializable] public class CustomData { public int a; public int b; public int c; } // 第二个参数为true,则会默认绘制所有子元素 EditorGUILayout.PropertyField(this.serializedObject.FindProperty("data"), true); EditorGUILayout.PropertyField(this.serializedObject.FindProperty("datas"), true);
- 绘制折叠动画
// 每个动画都需要一个AnimBool private AnimBool fadeGroup; private void OnEnable() { this.fadeGroup = new AnimBool(true); // 注册动画监听 this.fadeGroup.valueChanged.AddListener(this.Repaint); } private void OnDisable() { // 移除动画监听 this.fadeGroup.valueChanged.RemoveListener(this.Repaint); } // target控制动画开始播放 this.fadeGroup.target = EditorGUILayout.Foldout(this.fadeGroup.target, "BeginFadeGroup", true); // 系统使用tween渐变faded数值 if (EditorGUILayout.BeginFadeGroup(this.fadeGroup.faded)) { EditorGUILayout.BoundsField("BoundsField", new Bounds()); EditorGUILayout.BoundsIntField("BoundsIntField", new BoundsInt()); } // begin - end 之间元素会进行动画 EditorGUILayout.EndFadeGroup(); // 又一种风格的空格 GUILayout.Space(10);
Editor中依然可以使用GUILayout的功能。
- 绘制水平布局
// 水平布局,并使用box皮肤 EditorGUILayout.BeginHorizontal(GUI.skin.box); // 使用了GUILayout去覆盖自动布局的设置 EditorGUILayout.LabelField("This is BeginHorizontal", GUILayout.MaxWidth(150.0f)); EditorGUILayout.DelayedDoubleField(11.1f); EditorGUILayout.DelayedTextField("DelayedTextField"); EditorGUILayout.DropdownButton(GUIContent.none, FocusType.Passive); EditorGUILayout.EndHorizontal(); EditorGUILayout.Separator();
- 绘制垂直布局
EditorGUILayout.BeginVertical(GUI.skin.box); // 依然可以使用GUILayout来显示控件 GUILayout.Box("This is BeginVertical"); EditorGUILayout.ColorField("ColorField", Color.yellow); EditorGUILayout.CurveField("CurveField", new AnimationCurve(), GUILayout.MaxWidth(400.0f)); EditorGUILayout.HelpBox("HelpBox", MessageType.Info); EditorGUILayout.EnumFlagsField("EnumFlagsField", this.types); EditorGUILayout.EnumPopup("EnumPopup", this.types); this.selectOption = EditorGUILayout.IntPopup("IntPopup", (int) this.selectOption, new string[] {"0", "1", "2"}, new int[] {0, 1, 2}); this.selectOption = EditorGUILayout.IntSlider("IntSlider", (int) this.selectOption, 0, 2); this.selectOption = EditorGUILayout.MaskField("MaskField", (int) this.selectOption, new string[] {"mask1", "mask2", "mask3"}); this.selectOption = EditorGUILayout.Popup("Popup", (int) this.selectOption, new string[] {"s1", "s2", "s3"}); EditorGUILayout.EndVertical(); EditorGUILayout.Separator();
- 绘制滚动区域
// 超出内容会出现滚动条 this.scrollPos = EditorGUILayout.BeginScrollView(this.scrollPos, GUI.skin.box); GUILayout.Box("this is BeginScrollView"); EditorGUILayout.TextArea("this is TextArea"); EditorGUILayout.RectField("RectField", new Rect()); this.toggle = EditorGUILayout.Toggle("Toggle", this.toggle); // 在toggleGroup之间的会被整体设置可用性 this.toggleGroup = EditorGUILayout.BeginToggleGroup("BeginToggleGroup", this.toggleGroup); GUILayout.Button("Btn1"); GUILayout.Button("Btn2"); GUILayout.Button("Btn3"); EditorGUILayout.EndToggleGroup(); EditorGUILayout.EndScrollView();
总结
- PropertyField方法是用来显示已有属性控件的,第二个参数为true则绘制所有子元素。
- Delayed开头的Field方法,是延迟控件。回车确定后,数值才会返回。
- 其它Field方法是用来绘制自定义控件的。
- Begin - End 是区域绘制控件,在之间的绘制会被整体控制。
- 可以混合使用GUI和GUILayout以及EditorGUI进行绘制。
- GUIContent 提供了重写控件名字和添加提示的机会。
- GUILayoutOption 是覆盖自动布局的设置,属性来自于GUILayout类。