当前位置:   article > 正文

Unity 编辑器扩展总结 七:数组或list集合的显示方式_editorguilayout展示list

editorguilayout展示list

编辑器扩展总结

工欲善其事必先利其器

引言: 在项目开发中,编辑器扩展为开发者提供了开发自定义工具的功能,让开发者更加便利地使用编辑器开发项目。近期小生一直在学习编辑器扩展的知识,发现网络上关于编辑器知识点的博客较为零散且混乱。当然,有一些大佬已经总结的很好了,小生这就算是狗尾续貂,主要目的为自我学习,近期会整理一系列编辑器相关的博客,分享给每一位在学习道路上奋斗的童鞋。如若博客中存在错误,还请大佬们不吝赐教。所有参考的博客或者视频来源将在文末展示。
开发版本: Unity 2018.1.3f1

相关博客传送门
一、编辑器开发入门

二、编辑器的相关特性

三、自定义Inspector面板

四、创建编辑器窗体

五、Gizmos辅助调试工具

六、扩展Scene视图

七、数组或list集合的显示方式

八、EditorPrefs、ScriptableObject、Undo

九、GUIStyle、GUISkin

十、AssetPostprocessor资源导入管线

数组或list集合的显示方式

通过PropertyField简单显示数组或者集合

using System.Collections.Generic;
using UnityEngine;

public class InspectorExample : MonoBehaviour
{
    //序列化
    [SerializeField] 
    public int[] intArray;
    [SerializeField]
    public List<string> stringList;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
using UnityEditor;

[CustomEditor(typeof(InspectorExample))]
public class InspectorExampleEditor : Editor
{
    private SerializedProperty intArray;
    private SerializedProperty stringList;

    private void OnEnable()
    {
        intArray = serializedObject.FindProperty("intArray");
        stringList = serializedObject.FindProperty("stringList");
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        EditorGUILayout.PropertyField(intArray,true);
        EditorGUILayout.PropertyField(stringList, true);
        serializedObject.ApplyModifiedProperties();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

ReorderableList实现可排序列表

ReorderableList可以实现通过鼠标拖动,修改列表元素的排列顺序,注意其命名空间为UnityEditorInternal

实现简单功能

目标类挂载在场景对象上

using System.Collections.Generic;
using UnityEngine;

public class TargetExample : MonoBehaviour
{
   [SerializeField]
   public List<string> stringArray;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

编辑器类放在Editor文件夹中

using UnityEngine;
using UnityEditor;
using UnityEditorInternal;

[CustomEditor(typeof(TargetExample))]
public class TargetExampleEditor : Editor
{
   private ReorderableList _stringArray;

   private void OnEnable()
   {
       _stringArray = new ReorderableList(serializedObject, serializedObject.FindProperty("stringArray")
           , true, true, true, true);

       //自定义列表名称
       _stringArray.drawHeaderCallback = (Rect rect) =>
       {
           GUI.Label(rect, "StringArray");
       };

       //自定义绘制列表元素
       _stringArray.drawElementCallback = (Rect rect,int index,bool selected,bool focused) =>
       {
           //根据index获取对应元素
           SerializedProperty item = _stringArray.serializedProperty.GetArrayElementAtIndex(index);
           rect.height = EditorGUIUtility.singleLineHeight;
           rect.y += 2;
           EditorGUI.PropertyField(rect, item, new GUIContent("Element "+index));
       };

       //当添加新元素时的回调函数,自定义新元素的值
       _stringArray.onAddCallback = (ReorderableList list) =>
       {
           if (list.serializedProperty != null)
           {
               list.serializedProperty.arraySize++;
               list.index = list.serializedProperty.arraySize - 1;
               SerializedProperty item = list.serializedProperty.GetArrayElementAtIndex(list.index);
               item.stringValue = "Default Value";
           }
           else
           {
               ReorderableList.defaultBehaviours.DoAddButton(list);
           }
       };

       //当删除元素时候的回调函数,实现删除元素时,有提示框跳出
       _stringArray.onRemoveCallback = (ReorderableList list) =>
       {
           if (EditorUtility.DisplayDialog("Warnning","Do you want to remove this element?","Remove","Cancel"))
           {
               ReorderableList.defaultBehaviours.DoRemoveButton(list);
           }
       };
   }

   public override void OnInspectorGUI()
   {
       serializedObject.Update();
       //自动布局绘制列表
       _stringArray.DoLayoutList();
       serializedObject.ApplyModifiedProperties();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
实现复杂功能,通过PropertyDrawer绘制列表元素
using System.Collections.Generic;
using UnityEngine;

public class TargetExample : MonoBehaviour
{
   [SerializeField]
   public List<PlayerItem> playerItemArray = new List<PlayerItem>();   
}

[System.Serializable]
public class PlayerItem  
{
   [SerializeField]
   public Texture icon;
   [SerializeField]
   public GameObject prefab;
   [SerializeField]
   public string name;
   [SerializeField]
   public int attack; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

[CustomEditor(typeof(TargetExample))]
public class TargetExampleEditor : Editor
{
   private ReorderableList _playerItemArray;

   private void OnEnable()
   {
       _playerItemArray = new ReorderableList(serializedObject, serializedObject.FindProperty("playerItemArray")
           , true, true, true, true);

       //自定义列表名称
       _playerItemArray.drawHeaderCallback = (Rect rect) =>
       {
           GUI.Label(rect, "Player Array");
       };

       //定义元素的高度
       _playerItemArray.elementHeight = 68;

       //自定义绘制列表元素
       _playerItemArray.drawElementCallback = (Rect rect,int index,bool selected,bool focused) =>
       {
           //根据index获取对应元素 
           SerializedProperty item = _playerItemArray.serializedProperty.GetArrayElementAtIndex(index);
           rect.height -=4;
           rect.y += 2;
           EditorGUI.PropertyField(rect, item,new GUIContent("Index "+index));
       };

       //当删除元素时候的回调函数,实现删除元素时,有提示框跳出
       _playerItemArray.onRemoveCallback = (ReorderableList list) =>
       {
           if (EditorUtility.DisplayDialog("Warnning","Do you want to remove this element?","Remove","Cancel"))
           {
               ReorderableList.defaultBehaviours.DoRemoveButton(list);
           }
       };
   }

   public override void OnInspectorGUI()
   {
       serializedObject.Update();
       //自动布局绘制列表
       _playerItemArray.DoLayoutList();
       serializedObject.ApplyModifiedProperties();
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

目前实现效果如下:
效果

通过PropertyDrawer来绘制PlayerItem的样式,注意这是对PlayerItem类的绘制,不是TargetExample类。同样是编辑器类,需要放在Editor文件夹下

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(PlayerItem))] 
public class TargetExampleDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        using (new EditorGUI.PropertyScope(position,label,property))
        {
            //设置属性名宽度
            EditorGUIUtility.labelWidth = 60;
            position.height = EditorGUIUtility.singleLineHeight;

            var iconRect = new Rect(position)
            {
                width = 64,
                height = 64
            };

            var prefabRect = new Rect(position)
            {
                width = position.width - 80,
                x = position.x + 80
            };

            var nameRect = new Rect(prefabRect) 
            {
                y = prefabRect.y + EditorGUIUtility.singleLineHeight + 5
            };

            var attackSliderRect = new Rect(nameRect)
            {
                y = nameRect.y + EditorGUIUtility.singleLineHeight + 5
            };

            var iconProperty = property.FindPropertyRelative("icon");
            var prefabProperty = property.FindPropertyRelative("prefab"); 
            var nameProperty = property.FindPropertyRelative("name");
            var attackProperty = property.FindPropertyRelative("attack");

            iconProperty.objectReferenceValue = EditorGUI.ObjectField(iconRect, iconProperty.objectReferenceValue, typeof(Texture), false);
            nameProperty.stringValue = EditorGUI.TextField(nameRect, nameProperty.displayName,nameProperty.stringValue);
            prefabProperty.objectReferenceValue = EditorGUI.ObjectField(prefabRect, prefabProperty.objectReferenceValue,typeof(GameObject),false);
            attackProperty.intValue = EditorGUI.IntSlider(attackSliderRect, attackProperty.intValue,0,100);
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

实现效果如下:
效果展示

拓展:添加下拉菜单

实现添加元素的时候,出现下拉菜单,可以在场景中自动生成Assets/Prefabs文件下的预制体,并完成自动赋值
效果展示
效果展示

在TargetExampleEditor中定义枚举和结构体

public enum PrefabType
{
    Player,
    Enemy, 
}

public struct Creation
{
    public PrefabType prefabType;
    public string path; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

//通过委托onAddDropdownCallback和GenericMenu实现下拉列表功能

_playerItemArray.onAddDropdownCallback = (Rect rect, ReorderableList list) =>
{
    GenericMenu menu = new GenericMenu();
    var guids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Prefabs/Player" });
    foreach (var guid in guids)
    {
        var path = AssetDatabase.GUIDToAssetPath(guid);
        menu.AddItem(new GUIContent("Player/" + System.IO.Path.GetFileNameWithoutExtension(path))
            , false, ClickHandler, new Creation() { prefabType = PrefabType.Player, path = path });
    }
    //添加分割线
    menu.AddSeparator("");
    guids = AssetDatabase.FindAssets("t:Prefab", new string[] { "Assets/Prefabs/Enemy" });
    foreach (var guid in guids)
    {
        var path = AssetDatabase.GUIDToAssetPath(guid);
        menu.AddItem(new GUIContent("Enemy/" + System.IO.Path.GetFileNameWithoutExtension(path))
            , false, ClickHandler, new Creation() { prefabType = PrefabType.Enemy, path = path });
    }
    //显示鼠标下方的菜单
    menu.ShowAsContext();
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

//添加GenericMenu的回调函数

 private void ClickHandler(object target)
{
    Creation creation = (Creation)target;
    int index = _playerItemArray.serializedProperty.arraySize;
    _playerItemArray.serializedProperty.arraySize++;
    _playerItemArray.index = index;
    SerializedProperty element = _playerItemArray.serializedProperty.GetArrayElementAtIndex(index);

    switch (creation.prefabType)
    {
        case PrefabType.Player:
            SpawnCharacter(creation,element,90);
            break;
        case PrefabType.Enemy:
            SpawnCharacter(creation, element, 80);
            break;
    }

    serializedObject.ApplyModifiedProperties();
}

private void SpawnCharacter(Creation creation, SerializedProperty element,int atk) 
{
    GameObject character = AssetDatabase.LoadAssetAtPath<GameObject>(creation.path);

    GameObject obj = GameObject.Instantiate(character);
    obj.name = character.name;

    SerializedProperty prefabPreperty = element.FindPropertyRelative("prefab");
    SerializedProperty iconPreperty = element.FindPropertyRelative("icon");
    SerializedProperty namePreperty = element.FindPropertyRelative("name");
    SerializedProperty attackPreperty = element.FindPropertyRelative("attack");

    prefabPreperty.objectReferenceValue = character;
    iconPreperty.objectReferenceValue = GetPreviewTex(character);
    namePreperty.stringValue = character.name;
    attackPreperty.intValue = atk;
}

//获取预制体的预览图
private Texture GetPreviewTex(GameObject obj)
{
    return AssetPreview.GetAssetPreview(obj) as Texture;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

ReorderableList的委托

名称描述
drawHeaderCallback绘制表头回调
drawFooterCallback绘制尾部回调
drawElementCallback绘制元素回调
drawElementBackgroundCallback绘制元素背景回调
onReorderCallback重新排序回调
onSelectCallback选中回调
onAddCallback添加按钮回调
onAddDropdownCallback添加下拉选项回调
onRemoveCallback移除元素回调
onMouseUpCallback鼠标抬起回调
onCanRemoveCallback是否显示可移除按钮回调
onChangedCallback列表改变回调

参考资料

unity make your lists functional with reorderablelis
Unity编辑器拓展之二:ReorderableList可重新排序的列表框(复杂使用)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/93003
推荐阅读