工欲善其事必先利其器
引言: 在项目开发中,编辑器扩展为开发者提供了开发自定义工具的功能,让开发者更加便利地使用编辑器开发项目。近期小生一直在学习编辑器扩展的知识,发现网络上关于编辑器知识点的博客较为零散且混乱。当然,有一些大佬已经总结的很好了,小生这就算是狗尾续貂,主要目的为自我学习,近期会整理一系列编辑器相关的博客,分享给每一位在学习道路上奋斗的童鞋。如若博客中存在错误,还请大佬们不吝赐教。所有参考的博客或者视频来源将在文末展示。
开发版本: Unity 2018.1.3f1
相关博客传送门
一、编辑器开发入门
二、Gizmos辅助调试工具
三、编辑器的相关特性
四、自定义Inspector面板
五、数组或list集合的显示方式
using System.Collections.Generic;
using UnityEngine;
public class InspectorExample : MonoBehaviour
{
//序列化
[SerializeField]
public int[] intArray;
[SerializeField]
public List 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可以实现通过鼠标拖动,修改列表元素的排列顺序,注意其命名空间为UnityEditorInternal
目标类挂载在场景对象上
using System.Collections.Generic;
using UnityEngine;
public class TargetExample : MonoBehaviour
{
[SerializeField]
public List 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();
}
}
using System.Collections.Generic;
using UnityEngine;
public class TargetExample : MonoBehaviour
{
[SerializeField]
public List playerItemArray = new List();
}
[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(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可重新排序的列表框(复杂使用)