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

编辑器扩展总结

工欲善其事必先利其器

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

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

二、Gizmos辅助调试工具

三、编辑器的相关特性

四、自定义Inspector面板

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

数组或list集合的显示方式

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

using System.Collections.Generic;
using UnityEngine;

public class InspectorExample : MonoBehaviour
{
    //序列化
    [SerializeField] 
    public int[] intArray;
    [SerializeField]
    public List<string> stringList;
}
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();
    }
}

ReorderableList实现可排序列表

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

实现简单功能

目标类挂载在场景对象上

using System.Collections.Generic;
using UnityEngine;

public class TargetExample : MonoBehaviour
{
   [SerializeField]
   public List<string> stringArray;
}

编辑器类放在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();
   }
}

实现复杂功能,通过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; 
}
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();
   }
}

目前实现效果如下:
效果

通过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);
        }
    }
}

实现效果如下:
效果展示

拓展:添加下拉菜单

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

在TargetExampleEditor中定义枚举和结构体

public enum PrefabType
{
    Player,
    Enemy, 
}

public struct Creation
{
    public PrefabType prefabType;
    public string path; 
}

//通过委托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();
};

//添加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;
}

ReorderableList的委托

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

参考资料

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

猜你喜欢

转载自blog.csdn.net/qq_35361471/article/details/84715930