Unity自定义数组在Inspector窗口的显示方式

了解

  1. 单行高度:EditorGUIUtility.singleLineHeight
  2. 获取 PropertyField 控件所需的高度:EditorGUI.GetPropertyHeight
  3. 属性是否在Inspector窗口展开:SerializedProperty.isExpanded
  4. 可重新排序列表类:ReorderableList
  5. 绘制纯色矩形:EditorGUI.DrawRect

示例

使用版本:2021.3.6f1c1
此版本的Unity已经实现数组元素添加,移除,移动功能。

自定义类

[System.Serializable]
public class Test 
{
    public string name;
    public int age;
}

数据类

定义一个数组,数组元素为自定义类

using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu]
public class TestSO : ScriptableObject
{
    public List<Test> testList;
}

数据类文件默认显示如下

在这里插入图片描述

修改数据类显示

数据元素内容显示为一行

注意:不能使用自动布局api

using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(Test))]
public class TestDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var spName = property.FindPropertyRelative("name");
        var spAge = property.FindPropertyRelative("age");

        var rect1 = position;
        var rect2 = position;

        var indentlevel = EditorGUI.indentLevel;
        var lableWidth = EditorGUIUtility.labelWidth;
        EditorGUI.indentLevel = 2;
        EditorGUIUtility.labelWidth = 60;

        rect1.width = position.width / 2;
        rect1.height = EditorGUIUtility.singleLineHeight;

        rect2.width = position.width / 2;
        rect2.height = EditorGUIUtility.singleLineHeight;

        rect2.x = position.width / 2+40;
        rect2.y = rect1.y;

        EditorGUI.PropertyField(rect1, spName, new GUIContent("名字"));
        EditorGUI.PropertyField(rect2, spAge, new GUIContent("年龄"));

        EditorGUI.indentLevel = indentlevel;
        EditorGUIUtility.labelWidth = lableWidth;

        //EditorGUI.DrawRect(rect1, Color.green);
        //EditorGUI.DrawRect(rect2, Color.gray);
    }

	//获取属性高度
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return base.GetPropertyHeight(property, label);
    }
}

修改后显示效果如下图所示
在这里插入图片描述

TestList修改为指定名称;Element修改为指定名称

using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
[CustomEditor(typeof(TestSO))]
public class TestSOEditor : Editor
{
    SerializedProperty sPTestList;
    ReorderableList arrayList;

    private void OnEnable()
    {
        sPTestList = serializedObject.FindProperty("testList");

        if (arrayList == null)
        {
            arrayList = new ReorderableList(serializedObject, sPTestList, true, true, true, true);    
            //绘制Header
            arrayList.drawHeaderCallback += DrawHeader;
            //绘制数组元素
            arrayList.drawElementCallback += DrawElement;
            //获取数组的高度
            arrayList.elementHeightCallback += DrawElementHeight;
        }
    }

    void DrawHeader(Rect rect)
    {
        EditorGUI.LabelField(rect, "测试列表");
    }

    void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
    {
        var element = sPTestList.GetArrayElementAtIndex(index);
        var arrowRect = rect;

        int indentLevel = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 1;
        arrowRect.height = EditorGUIUtility.singleLineHeight;
        element.isExpanded = EditorGUI.Foldout(arrowRect, element.isExpanded, new GUIContent("元素" + index));
        EditorGUI.indentLevel = indentLevel;

        //EditorGUI.DrawRect(rect, Color.red);
        //EditorGUI.DrawRect(arrowRect, Color.blue);

        if (element.isExpanded)
        {
            rect.y += arrowRect.height;
            EditorGUI.PropertyField(rect, element);
        }
    }

    float DrawElementHeight(int index)
    {
        var element = sPTestList.GetArrayElementAtIndex(index);
        var height = EditorGUIUtility.singleLineHeight;//折叠箭头的高度   
        if (element.isExpanded)//如果元素展开 获取展开内容的高度和箭头的高度之和
            height += EditorGUI.GetPropertyHeight(element);
        return height;
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        arrayList.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
    }
}

修改后如下图所示
在这里插入图片描述

示例二

高度计算放在PropertyDrawer的OnGUI中

自定义类

显示折叠箭头,数据元素内容显示为一行,Element修改为指定名称

using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(Test))]
public class TestDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var spName = property.FindPropertyRelative("name");
        var spAge = property.FindPropertyRelative("age");

        var rect0 = position;
        rect0.height = EditorGUIUtility.singleLineHeight;

        var indentlevel = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 1;
      
        property.isExpanded = EditorGUI.Foldout(rect0, property.isExpanded, label);
      
        EditorGUI.indentLevel = indentlevel;

        if (property.isExpanded)
        {
            var rect1 = position;
            var rect2 = position;

            indentlevel = EditorGUI.indentLevel;
            var lableWidth = EditorGUIUtility.labelWidth;
            EditorGUI.indentLevel = 2;
            EditorGUIUtility.labelWidth = 60;

            rect1.width = position.width / 2;
            rect1.height = EditorGUIUtility.singleLineHeight;
            rect1.y += EditorGUIUtility.singleLineHeight;

            rect2.width = position.width / 2;
            rect2.height = EditorGUIUtility.singleLineHeight;

            rect2.x = position.width / 2 + 40;
            rect2.y = rect1.y;

            EditorGUI.PropertyField(rect1, spName, new GUIContent("名字"));
            EditorGUI.PropertyField(rect2, spAge, new GUIContent("年龄"));

            EditorGUI.indentLevel = indentlevel;
            EditorGUIUtility.labelWidth = lableWidth;
            //EditorGUI.DrawRect(rect1, Color.green);
            //EditorGUI.DrawRect(rect2, Color.gray);
        }
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        if (property.isExpanded)
            return EditorGUIUtility.singleLineHeight * 2;
        else
            return EditorGUIUtility.singleLineHeight;
    }
}

数据类

TestList修改为指定名称

using UnityEngine;
using UnityEditor;
using UnityEditorInternal;
[CustomEditor(typeof(TestSO))]
public class TestSOEditor : Editor
{
    SerializedProperty sPTestList;
    ReorderableList arrayList;

    private void OnEnable()
    {
        sPTestList = serializedObject.FindProperty("testList");
        if (arrayList == null)
        {
            arrayList = new ReorderableList(serializedObject, sPTestList, true, true, true, true);
            //绘制Header
            arrayList.drawHeaderCallback += DrawHeader;
            //绘制数组元素
            arrayList.drawElementCallback += DrawElement;
            //绘制元素高度
            arrayList.elementHeightCallback += DrawHeight;

            void DrawHeader(Rect rect)
            {
                EditorGUI.LabelField(rect, "测试列表");
            }

            void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
            {
            	//参数会传递给PropertyDrawer的OnGUI 
                EditorGUI.PropertyField(rect, sPTestList.GetArrayElementAtIndex(index), new GUIContent("元素 " + index));
            }

            float DrawHeight(int index)
            {
                return EditorGUI.GetPropertyHeight(sPTestList.GetArrayElementAtIndex(index));
            }
        }
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        arrayList.DoLayoutList();
        serializedObject.ApplyModifiedProperties();
    }
}

参考

看完后理解列表的高度如何计算

### 如何在 Unity Inspector 中编辑和更改属性 #### 自定义 Inspector 基础设置 为了能够在 UnityInspector 窗口中修改对象属性,通常会创建自定义的 `Editor` 类继承于 `UnityEditor.Editor` 并重写特定的方法。对于希望保留原有 GUI 绘制逻辑的同时添加额外功能的情况,可以通过调用 `m_Editor.OnInspectorGUI()` 来确保原始布局不被破坏[^1]。 #### 使用 SerializedProperty 进行安全访问 当涉及到具体属性的操作时,推荐利用 `SerializedProperty` 对象而非直接操作组件中的成员变量。这不仅提供了更安全的方式去读取或更新数据,而且还能更好地支持 Undo/Redo 功能以及 Prefab 实例化后的同步机制[^2]。 ```csharp using UnityEngine; using UnityEditor; [CustomEditor(typeof(MyComponent))] public class MyComponentEditor : Editor { public override void OnInspectorGUI() { DrawDefaultInspector(); var myProp = serializedObject.FindProperty("myField"); EditorGUILayout.PropertyField(myProp); serializedObject.ApplyModifiedProperties(); } } ``` #### 属性标签本地化处理 针对那些需要展示给用户的界面文字(比如字段名称),可以采用自定义特性的方式来实现在不影响代码内部命名的前提下对外呈现不同的描述信息。例如,通过 `[DisplayNameAttribute]` 或者其他类似的解决方案可以在保持 C# 变量名为英文的情况下,在 Inspector 上显示对应的中文说明[^3]。 #### 处理复杂类型的可视化调整 面对诸如数组、列表这样的复合型数据结构,默认情况下它们可能会以一种不太直观的形式展现出来。此时可考虑覆写 `GetPropertyHeight` 方法来自适应地控制这些元素所占用的空间大小,从而改善用户体验并防止潜在的 UI 排列问题发生[^4]。 #### 数据持久性和运行时一致性维护 最后需要注意的是,某些时候即使是在编辑状态下成功改变了某个私有字段的内容,但在进入 Play Mode 后却发现其并未生效。为了避免这种情况的发生,应当适时调用 `EditorUtility.SetDirty(object)` 函数标记目标实例已发生变化;另外也可以尝试将原本受保护级别的域声明为公共可见或是附加 `[SerializeField]` 特性以便让引擎能够正常保存改动过的数值[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值