Unity编辑器扩展之Component的Inspector面板

组件Component在Inspector面板上的显示是我们最常用到的功能,而在Inspector 面板中显示出来的其实就是序列化的数据。

下面先介绍成员变量的显示,解释几个特性的作用;然后介绍自定义inspector面板的流程,通过一个 NavMeshSurface Component 的例子来介绍常用的一些函数。

Inspector——检视面板,可被检视的数据——being inspected 的都是序列化的数据。

目录

1.默认Inspector面板——如何控制显示成员变量

2.自定义Inspector——如何自定义

0.[CanEditMultipleObjects]特性

1.[CustomEditor(typeof(NavMeshSurface))]

2.继承自基类,Editor类

0)类摘要

1)target成员变量

2)serializedObject 成员变量

3)DrawDefaultInspector函数 保留默认属性

3.EditorGUILayout 绘制类

0).类摘要:

1)XXXField 函数

2)绘制数组属性

3)绘制折叠效果——Foldout函数

4)绘制水平,垂直,滚动布局

5)待看


1.默认Inspector面板——如何控制显示成员变量

如果不自定义inspector面板,只是想要 让一些 成员变量 在Inspector中显示,方便使用拖动的方式进行初始化以及查看其值的变化,就直接在 component 类中 加一些特性就行了,不需要单独再写一个Editor类。

那么对于成员变量在Inspector的显示与否取决于(显示在Inspector面板下面的变量,就是会被序列化的变量。):

1) 该变量的访问权限:public变量直接可以在Inspector面板中显示并编辑,而private和protected不行。(注意,1)不是所有的public变量都是可序列化的,比如const 和 static  2)变量类型是自定义的类不受这个影响,如果想要序列化,要加上[Serializable])

2) 是否有一些Attribute修饰该变量,相关的Attribute以及作用如下:

  • [SerializeField]:一般修饰 private和protected 变量,来达到序列化私有和保护变量的目的,从而可以在Inspector面板中显示和编辑。防止仅仅想让其在Inspector面板中显示而导致public变量滥用。
  • [NonSerialized]:一般修饰public变量,来达到不被序列化的目的,且不在Inspector面板中显示
  • [HideInInspector]:一般修饰public变量,来达到不在Inspector面板中显示的目的。
  • [Serializable]:用在类的前面,表示该类可被序列化。

总的来说就是

1)被序列化的值可以在Inspector面板上显示,反之则不能。变量本身通过访问权限可以知道是否被序列,在此之外还可以使用[SerializeField] [NonSerialized] [Serializable] 这几个Attribute改变序列化与不被序列化。

2)被序列化的值也可以不在Inspector面板中显示,使用[HideInInspector]特性来隐藏。

3)不被序列化的值是不可以在Inspector面板中显示的。

2.自定义Inspector——如何自定义

需要写一个编辑器脚本,扩展更多的显示信息,提供丰富的界面操作。

下面通过扩展 NavMeshSurface 类的Inspector面板来简要介绍,编辑器脚本大体样子:

[CanEditMultipleObjects]
[CustomEditor(typeof(NavMeshSurface))]
class NavMeshSurfaceEditor : Editor
{

    public override void OnInspectorGUI()
    {
        // 使用 GUI类 GUILayout类
        // 使用 EditorGUI类 EditorGUILayout类 EditorUtility类  
    }

}

必须需要做的事:

1)继承Editor。
2)添加[CustomEditor(typeof(NavMeshSurface))]特性,告诉编辑器这个类是扩展NavMeshSurface 的Inspector。
3)覆写OnInspectorGUI方法,实现自定义的扩展。

贴一下NavMeshSurface序列化的成员变量,后续方便查看:

    public class NavMeshSurface : MonoBehaviour
    {
        [SerializeField]
        int m_AgentTypeID;
        public int agentTypeID { get { return m_AgentTypeID; } set { m_AgentTypeID = value; } }

        [SerializeField]
        CollectObjects m_CollectObjects = CollectObjects.All;
        public CollectObjects collectObjects { get { return m_CollectObjects; } set { m_CollectObjects = value; } }

        [SerializeField]
        Vector3 m_Size = new Vector3(10.0f, 10.0f, 10.0f);
        public Vector3 size { get { return m_Size; } set { m_Size = value; } }

        [SerializeField]
        Vector3 m_Center = new Vector3(0, 2.0f, 0);
        public Vector3 center { get { return m_Center; } set { m_Center = value; } }

        [SerializeField]
        LayerMask m_LayerMask = ~0;
        public LayerMask layerMask { get { return m_LayerMask; } set { m_LayerMask = value; } }

        [SerializeField]
        NavMeshCollectGeometry m_UseGeometry = NavMeshCollectGeometry.RenderMeshes;
        public NavMeshCollectGeometry useGeometry { get { return m_UseGeometry; } set { m_UseGeometry = value; } }

        [SerializeField]
        int m_DefaultArea;
        public int defaultArea { get { return m_DefaultArea; } set { m_DefaultArea = value; } }

        [SerializeField]
        bool m_IgnoreNavMeshAgent = true;
        public bool ignoreNavMeshAgent { get { return m_IgnoreNavMeshAgent; } set { m_IgnoreNavMeshAgent = value; } }

        [SerializeField]
        bool m_IgnoreNavMeshObstacle = true;
        public bool ignoreNavMeshObstacle { get { return m_IgnoreNavMeshObstacle; } set { m_IgnoreNavMeshObstacle = value; } }

        [SerializeField]
        bool m_OverrideTileSize;
        public bool overrideTileSize { get { return m_OverrideTileSize; } set { m_OverrideTileSize = value; } }
        [SerializeField]
        int m_TileSize = 256;
        public int tileSize { get { return m_TileSize; } set { m_TileSize = value; } }
        [SerializeField]
        bool m_OverrideVoxelSize;
        public bool overrideVoxelSize { get { return m_OverrideVoxelSize; } set { m_OverrideVoxelSize = value; } }
        [SerializeField]
        float m_VoxelSize;
        public float voxelSize { get { return m_VoxelSize; } set { m_VoxelSize = value; } }

        // Currently not supported advanced options
        [SerializeField]
        bool m_BuildHeightMesh;
        public bool buildHeightMesh { get { return m_BuildHeightMesh; } set { m_BuildHeightMesh = value; } }

        // Reference to whole scene navmesh data asset.
        [UnityEngine.Serialization.FormerlySerializedAs("m_BakedNavMeshData")]
        [SerializeField]
        NavMeshData m_NavMeshData;
        public NavMeshData navMeshData { get { return m_NavMeshData; } set { m_NavMeshData = value; } }
    }

0.[CanEditMultipleObjects]特性

这个特性用于当选中多个物体,可以在Inspector面板中同时编辑多个类型相同的Component对象。

比如如下图,这两个物体都挂了NavMeshSurface这个Component,且这个Component的自定义Inspector面板具有[CanEditMultipleObjects]特性,就可以同时编辑了:

而如果没有写这个特性,则无法同时编辑,显示如下:

1.[CustomEditor(typeof(NavMeshSurface))]

CustomEditor 特性 自定义编辑器 

描述这是为哪一个运行时类型 定制的编辑器,这里就是 NavMeshSurface 这个Component。

2.继承自基类,Editor类

介绍几个常用的 成员变量:serializedObject 以及 target (targets)

以及常用的 函数:DrawDefaultInspector

0)类摘要

    //
    // 摘要:
    //     ///
    //     Base class to derive custom Editors from. Use this to create your own custom
    //     inspectors and editors for your objects.
    //     ///
    [RequiredByNativeCode]
    public class Editor : ScriptableObject, IPreviewable, IToolModeOwner
    {
        public Editor();

        //
        // 摘要:
        //     ///
        //     A SerializedObject representing the object or objects being inspected.
        //     ///
        public SerializedObject serializedObject { get; }
        //
        // 摘要:
        //     ///
        //     The object being inspected.
        //     ///
        public UnityEngine.Object target { get; set; }
        //
        // 摘要:
        //     ///
        //     An array of all the object being inspected.
        //     ///
        public UnityEngine.Object[] targets { get; }
        //
        // 摘要:
        //     ///
        //     Draw the built-in inspector.
        //     ///
        public bool DrawDefaultInspector();
    }

1)target成员变量

代表的是被检视的Object对象。

这个例子中的target就是 NavMeshSurface了

上面说到,被[CanEditMultipleObjects]特性修饰以后可以同时修改显示多个同类型的Object。所以targets成员变量,就是当被被[CanEditMultipleObjects]修饰了以后使用的。

2)serializedObject 成员变量

代表的是被检视的Object(target)的对应的序列化的对象。

这个例子中即为NavMeshComponent这个Object 对应的序列化对象。

下面介绍几个 SerializedObject 类的几个常用函数——Update、ApplyModifiedProperties、FindProperty 

0.摘要

    // 摘要:
    //     ///
    //     SerializedObject and SerializedProperty are classes for editing properties on
    //     objects in a completely generic way that automatically handles undo and styling
    //     UI for prefabs.
    //     ///
    public sealed class SerializedObject : IDisposable
    {
        //
        // 摘要:
        //     ///
        //     Update serialized object's representation.
        //     ///
        [GeneratedByOldBindingsGenerator]
        public void Update();
        //
        // 摘要:
        //     ///
        //     Apply property modifications.
        //     ///
        [GeneratedByOldBindingsGenerator]
        public bool ApplyModifiedProperties();
        //
        // 摘要:
        //     ///
        //     Find serialized property by name.
        //     ///
        //
        // 参数:
        //   propertyPath:
        public SerializedProperty FindProperty(string propertyPath);
    }

1. Update 和 ApplyModifiedProperties 函数

在 OnInspectorGUI 函数中 自定义绘制逻辑 的前后使用。

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        // 自定义绘制开始
        // balabala
        // 自定义绘制结束
        serializedObject.ApplyModifiedProperties();
    }

2. FindProperty 函数

FindProperty用来查找序列化对象中的已有属性。

输入为target中定义的可以被序列化的属性名,返回类型是 SerializedProperty。

    class NavMeshSurfaceEditor : Editor
    {
        SerializedProperty m_AgentTypeID;
        SerializedProperty m_BuildHeightMesh;
        SerializedProperty m_Center;
        SerializedProperty m_CollectObjects;
        SerializedProperty m_DefaultArea;
        SerializedProperty m_LayerMask;
        SerializedProperty m_OverrideTileSize;
        SerializedProperty m_OverrideVoxelSize;
        SerializedProperty m_Size;
        SerializedProperty m_TileSize;
        SerializedProperty m_UseGeometry;
        SerializedProperty m_VoxelSize;

        void OnEnable()
        {
            m_AgentTypeID = serializedObject.FindProperty("m_AgentTypeID");
            m_BuildHeightMesh = serializedObject.FindProperty("m_BuildHeightMesh");
            m_Center = serializedObject.FindProperty("m_Center");
            m_CollectObjects = serializedObject.FindProperty("m_CollectObjects");
            m_DefaultArea = serializedObject.FindProperty("m_DefaultArea");
            m_LayerMask = serializedObject.FindProperty("m_LayerMask");
            m_OverrideTileSize = serializedObject.FindProperty("m_OverrideTileSize");
            m_OverrideVoxelSize = serializedObject.FindProperty("m_OverrideVoxelSize");
            m_Size = serializedObject.FindProperty("m_Size");
            m_TileSize = serializedObject.FindProperty("m_TileSize");
            m_UseGeometry = serializedObject.FindProperty("m_UseGeometry");
            m_VoxelSize = serializedObject.FindProperty("m_VoxelSize");

            NavMeshVisualizationSettings.showNavigation++;
        }
    }

SerializedProperty 类 用来操作 target的属性的。

比如 “m_AgentTypeID” 在target中是一个int类型,那么就调用 SerializedProperty.intValue 进行获取其值 和 设置其值。

比如当选中多个物体的时候,就用SerializedProperty.hasMultipleDifferentValues 来判断是否值不一致。

比如用SerializedProperty.FindPropertyRelative 来查找相对于这个属性对象的子属性。

比如用SerializedProperty.isExpanded 来判断某一些具有子元素(数组,自定义序列化的结构体和类)的属性是否是展开状态还是收起状态。

摘要如下:

    //
    // 摘要:
    //     ///
    //     SerializedProperty and SerializedObject are classes for editing properties on
    //     objects in a completely generic way that automatically handles undo and styling
    //     UI for prefabs.
    //     ///
    public sealed class SerializedProperty : IDisposable
    {
        //
        // 摘要:
        //     ///
        //     Value of an integer property.
        //     ///
        public int intValue { get; set; }

        //
        // 摘要:
        //     ///
        //     Enum index of an enum property.
        //     ///
        public int enumValueIndex { get; set; }

        //
        // 摘要:
        //     ///
        //     Retrieves the SerializedProperty at a relative path to the current property.
        //     ///
        //
        // 参数:
        //   relativePropertyPath:
        public SerializedProperty FindPropertyRelative(string relativePropertyPath);

        //
        // 摘要:
        //     ///
        //     Does this property represent multiple different values due to multi-object editing?
        //     (Read Only)
        //     ///
        public bool hasMultipleDifferentValues { get; }


        //
        // 摘要:
        //     ///
        //     Is this property expanded in the inspector?
        //     ///
        public bool isExpanded { get; set; }
    }

3)DrawDefaultInspector函数 保留默认属性

即之前那些序列化的成员变量都会显示出来:

[CanEditMultipleObjects]
[CustomEditor(typeof(NavMeshSurface))]
class NavMeshSurfaceEditor : Editor
{

    public override void OnInspectorGUI()
    {
        // 保留原有
        base.DrawDefaultInspector();
        // 扩展新的
        // balabla
    }

}

如果没有加这一句,原来的默认属性(在NavMeshSurface中定义的序列化的那些变量)就会被忽略,不显示在面板上。

不加  base.DrawDefaultInspector(); 的Inspector面板

加了   base.DrawDefaultInspector();  的Inspector面板

3.EditorGUILayout 绘制类

0).类摘要:

    //
    // 摘要:
    //     ///
    //     Auto-layouted version of EditorGUI.
    //     ///
    public sealed class EditorGUILayout
    {
        //
        // 摘要:
        //     ///
        //     Make a small space between the previous control and the following.
        //     ///
        public static void Space();

        //
        // 摘要:
        //     ///
        //     Make a field for SerializedProperty.
        //     ///
        //
        // 参数:
        //   property:
        //     The SerializedProperty to make a field for.
        //
        //   label:
        //     Optional label to use. If not specified the label of the property itself is used.
        //     Use GUIContent.none to not display a label at all.
        //
        //   includeChildren:
        //     If true the property including children is drawn; otherwise only the control
        //     itself (such as only a foldout but nothing below it).
        //
        //   options:
        //     An optional list of layout options that specify extra layout properties. Any
        //     values passed in here will override settings defined by the style.<br> /// See
        //     Also: GUILayout.Width, GUILayout.Height, GUILayout.MinWidth, GUILayout.MaxWidth,
        //     GUILayout.MinHeight, /// GUILayout.MaxHeight, GUILayout.ExpandWidth, GUILayout.ExpandHeight.
        //
        // 返回结果:
        //     ///
        //     True if the property has children and is expanded and includeChildren was set
        //     to false; otherwise false.
        //     ///
        public static bool PropertyField(SerializedProperty property, params GUILayoutOption[] options);
        public static bool PropertyField(SerializedProperty property, bool includeChildren, params GUILayoutOption[] options);
        public static bool PropertyField(SerializedProperty property, GUIContent label, params GUILayoutOption[] options);
        public static bool PropertyField(SerializedProperty property, GUIContent label, bool includeChildren, params GUILayoutOption[] options);

        //
        // 摘要:
        //     ///
        //     Make a delayed text field for entering integers.
        //     ///
        //
        // 参数:
        //   label:
        //     Optional label to display in front of the int field.
        //
        //   value:
        //     The value to edit.
        //
        //   style:
        //     Optional GUIStyle.
        //
        //   options:
        //     An optional list of layout options that specify extra layout properties. Any
        //     values passed in here will override settings defined by the style.<br> /// See
        //     Also: GUILayout.Width, GUILayout.Height, GUILayout.MinWidth, GUILayout.MaxWidth,
        //     GUILayout.MinHeight, /// GUILayout.MaxHeight, GUILayout.ExpandWidth, GUILayout.ExpandHeight.
        //
        // 返回结果:
        //     ///
        //     The value entered by the user. Note that the return value will not change until
        //     the user has pressed enter or focus is moved away from the int field.
        //     ///
        public static int DelayedIntField(int value, params GUILayoutOption[] options);
        public static void DelayedIntField(SerializedProperty property, params GUILayoutOption[] options);
        public static int DelayedIntField(int value, GUIStyle style, params GUILayoutOption[] options);
        public static int DelayedIntField(string label, int value, params GUILayoutOption[] options);
        。。。。。等等重载函数

        //
        // 摘要:
        //     ///
        //     Make a label with a foldout arrow to the left of it.
        //     ///
        //
        // 参数:
        //   foldout:
        //     The shown foldout state.
        //
        //   content:
        //     The label to show.
        //
        //   style:
        //     Optional GUIStyle.
        //
        //   toggleOnLabelClick:
        //     Whether to toggle the foldout state when the label is clicked.
        //
        // 返回结果:
        //     ///
        //     The foldout state selected by the user. If true, you should render sub-objects.
        //     ///
        [ExcludeFromDocs]
        public static bool Foldout(bool foldout, string content);
        。。。。。等等重载函数
    }

1)XXXField 函数

PropertyField :能够在Inspector上绘制属性控件,如果返回false表示属性控件不可用,或者处在不可见的状态。

1)有四个重载函数,可以自定义显示的字段名字,可以设置层级显示,可以设置布局属性。

2)会根据Property的类型,自动绘制不同的布局,比如下面例子的:

    绘制enum类型(m_CollectObjects,m_UseGeometry)

    绘制struct类型 UnityEngine.LayerMask(m_UseGeometry)

    绘制struct类型 UnityEngine.Vector3(m_Size,m_Center)

3)第二个参数为true则绘制所有子元素。

4)返回值,在绘制数组类型的时候会根据返回值来判断是否需要展看显示数组元素。

EditorGUILayout.Space();

EditorGUILayout.PropertyField(m_CollectObjects);
if ((CollectObjects)m_CollectObjects.enumValueIndex == CollectObjects.Volume)
{
    EditorGUI.indentLevel++;// 后续显示都增加一个缩进

    EditMode.DoEditModeInspectorModeButton(EditMode.SceneViewEditMode.Collider, "Edit Volume",
        EditorGUIUtility.IconContent("EditCollider"), GetBounds, this);
    EditorGUILayout.PropertyField(m_Size);
    EditorGUILayout.PropertyField(m_Center);

    EditorGUI.indentLevel--;// 恢复缩进
}
else
{
    if (editingCollider)
        EditMode.QuitEditMode();
}

EditorGUILayout.PropertyField(m_LayerMask, s_Styles.m_LayerMask);// 不用默认的属性名,自行设置:public readonly GUIContent m_LayerMask = new GUIContent("Include Layers");
EditorGUILayout.PropertyField(m_UseGeometry);

EditorGUILayout.Space();

EditorGUILayout.Space();

上述代码的绘制结果即为:

DelayedIntField:绘制的是Int类型的属性,显示的值是由用户输入的。常用来设置数组个数的时候用。其他的类似的Delayed开头的Field方法,均是延迟控件,回车确定后,数值才会返回,比如DelayedDoubleFieldDelayedTextField....。

其它Field方法:是用来绘制自定义控件的

EditorGUILayout.LabelField("This is BeginHorizontal", GUILayout.MaxWidth(150.0f));

EditorGUILayout.ColorField("ColorField", Color.yellow);
EditorGUILayout.CurveField("CurveField", new AnimationCurve(), GUILayout.MaxWidth(400.0f));

EditorGUILayout.EnumFlagsField("EnumFlagsField", enum类型变量);

EditorGUILayout.MaskField

EditorGUILayout.RectField("RectField", new Rect());
 

2)绘制数组属性

假设target有这么一个属性字段

        [SerializeField]
        int[] IntArray;

1.自定义绘制:

在Editor的OnInspectorGUI写以下逻辑绘制这个数组:

// 查找属性
var elements = this.serializedObject.FindProperty("IntArray");

// 属性元素可见,控件展开状态
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--;
}

2.默认绘制:

调用PropertyField的一个子元素,true即为默认绘制子元素

EditorGUILayout.PropertyField(elements,true);

3.效果

3)绘制折叠效果——Foldout函数

逻辑:

m_OverrideVoxelSize.isExpanded = EditorGUILayout.Foldout(m_OverrideVoxelSize.isExpanded, "Advanced");
if (m_OverrideVoxelSize.isExpanded)
{// balabala绘制

}

效果:

之前的数组也是具有折叠效果。如果要绘制动画效果的话:https://zhuanlan.zhihu.com/p/30837169

4)绘制水平,垂直,滚动布局

Begin - End 是区域绘制控件,在之间的绘制会被整体控制。

// 水平布局,并使用box皮肤
EditorGUILayout.BeginHorizontal(GUI.skin.box);

// 这之间的元素 都在同一行中

EditorGUILayout.EndHorizontal();

// 垂直布局,并使用box皮肤
EditorGUILayout.BeginVertical(GUI.skin.box);

// 这之间的元素 都在同一列中

EditorGUILayout.EndVertical();

this.scrollPos = EditorGUILayout.BeginScrollView(this.scrollPos, GUI.skin.box);

// 超出内容会出现滚动条

EditorGUILayout.EndScrollView();

5)待看

NavMeshComponentsGUIUtility.AreaPopup("Default Area", m_DefaultArea);

        public static void AreaPopup(string labelName, SerializedProperty areaProperty)
        {
            var areaIndex = -1;
            var areaNames = GameObjectUtility.GetNavMeshAreaNames();
            for (var i = 0; i < areaNames.Length; i++)
            {
                var areaValue = GameObjectUtility.GetNavMeshAreaFromName(areaNames[i]);
                if (areaValue == areaProperty.intValue)
                    areaIndex = i;
            }
            ArrayUtility.Add(ref areaNames, "");
            ArrayUtility.Add(ref areaNames, "Open Area Settings...");

            var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
            EditorGUI.BeginProperty(rect, GUIContent.none, areaProperty);

            EditorGUI.BeginChangeCheck();
            areaIndex = EditorGUI.Popup(rect, labelName, areaIndex, areaNames);

            if (EditorGUI.EndChangeCheck())
            {
                if (areaIndex >= 0 && areaIndex < areaNames.Length - 2)
                    areaProperty.intValue = GameObjectUtility.GetNavMeshAreaFromName(areaNames[areaIndex]);
                else if (areaIndex == areaNames.Length - 1)
                    NavMeshEditorHelpers.OpenAreaSettings();
            }

            EditorGUI.EndProperty();
        }

效果:

https://blog.csdn.net/tom_221x/article/details/79437561

发布了104 篇原创文章 · 获赞 44 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/u012138730/article/details/90110914