从该基类派生自定义属性绘制器。使用它为自己的可序列化类或具有自定义 PropertyAttribute 的脚本变量创建自定义绘制器。
属性绘制器有两种用途
如果您有一个自定义的可序列化类,您可以使用属性绘制器来控制它在 Inspector 中的外观。考虑下面脚本中的可序列化类 Ingredient
using System; using UnityEngine;
public enum IngredientUnit { Spoon, Cup, Bowl, Piece }
// Custom serializable class [Serializable] public class Ingredient { public string name; public int amount = 1; public IngredientUnit unit; }
public class Recipe : MonoBehaviour { public Ingredient potionResult; public Ingredient[] potionIngredients; }
使用自定义属性绘制器,Inspector 中 Ingredient 类的每次出现都可以更改。
您可以使用 CustomPropertyDrawer 属性将属性绘制器附加到可序列化类,并传入它是哪个可序列化类的绘制器。
您可以使用 UI Toolkit 或 IMGUI 来构建自定义属性绘制器。要使用 UI Toolkit 创建自定义属性绘制器,您必须重写 PropertyDrawer 类中的 PropertyDrawer.CreatePropertyGUI 。要使用 IMGUI 创建自定义属性绘制器,您必须重写 PropertyDrawer 类中的 PropertyDrawer.OnGUI 。
注意: 您无法在 IMGUI 内部运行 UI Toolkit。这意味着如果您的自定义属性绘制器只有 UI Toolkit 实现,它将无法在 IMGUI 自定义 Inspector 或父 IMGUI 自定义属性绘制器中工作。从 Unity 2022.2 开始,默认 Inspector 在自定义属性绘制器中完全使用 UI Toolkit。但是,如果属性绘制器是从自定义编辑器中调用,您可能仍然需要实现 IMGUI。在 2022.2 之前,建议您为每个属性绘制器实现 IMGUI 和 UI Toolkit 版本,或者确保它们仅在自定义 UI Toolkit Inspector 中使用。
以下是一个使用 UI Toolkit 编写的自定义属性绘制器的示例
using UnityEditor; using UnityEditor.UIElements; using UnityEngine.UIElements;
// IngredientDrawerUIE [CustomPropertyDrawer(typeof(Ingredient))] public class IngredientDrawerUIE : PropertyDrawer { public override VisualElement CreatePropertyGUI(SerializedProperty property) { // Create property container element. var container = new VisualElement();
// Create property fields. var amountField = new PropertyField(property.FindPropertyRelative("amount")); var unitField = new PropertyField(property.FindPropertyRelative("unit")); var nameField = new PropertyField(property.FindPropertyRelative("name"), "Fancy Name");
// Add fields to the container. container.Add(amountField); container.Add(unitField); container.Add(nameField);
return container; } }
以下是一个使用 IMGUI 编写的自定义属性绘制器的示例。比较 Inspector 中具有和不具有自定义属性绘制器的 Ingredient 属性的外观
Inspector 中的类,不具有(左)和具有(右)自定义属性绘制器。
using UnityEditor; using UnityEngine;
// IngredientDrawer [CustomPropertyDrawer(typeof(Ingredient))] public class IngredientDrawer : PropertyDrawer { // Draw the property inside the given rect public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { // Using BeginProperty / EndProperty on the parent property means that // prefab override logic works on the entire property. EditorGUI.BeginProperty(position, label, property);
// Draw label position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
// Don't make child fields be indented var indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0;
// Calculate rects var amountRect = new Rect(position.x, position.y, 30, position.height); var unitRect = new Rect(position.x + 35, position.y, 50, position.height); var nameRect = new Rect(position.x + 90, position.y, position.width - 90, position.height);
// Draw fields - pass GUIContent.none to each so they are drawn without labels EditorGUI.PropertyField(amountRect, property.FindPropertyRelative("amount"), GUIContent.none); EditorGUI.PropertyField(unitRect, property.FindPropertyRelative("unit"), GUIContent.none); EditorGUI.PropertyField(nameRect, property.FindPropertyRelative("name"), GUIContent.none);
// Set indent back to what it was EditorGUI.indentLevel = indent;
EditorGUI.EndProperty(); } }
PropertyDrawer 的另一个用途是改变具有自定义 PropertyAttribute 的脚本中成员的外观。假设您希望将脚本中的浮点数或整数限制在某个范围内,并在 Inspector 中将其显示为滑块。使用名为 RangeAttribute 的内置 PropertyAttribute ,您可以做到这一点
using UnityEngine; using System.Collections;
public class ExampleClass : MonoBehaviour { // Show this float in the Inspector as a slider between 0 and 10 [Range(0.0F, 10.0F)] public float myFloat = 0.0F; }
您也可以创建自己的 PropertyAttribute 。我们将使用 RangeAttribute 的代码作为示例。该属性必须扩展 PropertyAttribute 类。如果需要,您的属性可以接受参数并将它们存储为公共成员变量。
// This is not an editor script. The property attribute class should be placed in a regular script file. using UnityEngine;
public class RangeAttribute : PropertyAttribute { public float min; public float max;
public RangeAttribute(float min, float max) { this.min = min; this.max = max; } }
现在您有了属性,您需要制作一个属性绘制器来绘制具有该属性的属性。绘制器必须扩展 PropertyDrawer 类,并且必须具有 CustomPropertyDrawer 属性来告诉它它是哪个属性的绘制器。以下是一个使用 IMGUI 的示例
// The property drawer class should be placed in an editor script, inside a folder called Editor.
// Tell the RangeDrawer that it is a drawer for properties with the RangeAttribute. using UnityEngine; using UnityEditor;
[CustomPropertyDrawer(typeof(RangeAttribute))] public class RangeDrawer : PropertyDrawer { // Draw the property inside the given rect public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { // First get the attribute since it contains the range for the slider RangeAttribute range = attribute as RangeAttribute;
// Now draw the property as a Slider or an IntSlider based on whether it's a float or integer. if (property.propertyType == SerializedPropertyType.Float) EditorGUI.Slider(position, property, range.min, range.max, label); else if (property.propertyType == SerializedPropertyType.Integer) EditorGUI.IntSlider(position, property, Convert.ToInt32(range.min), Convert.ToInt32(range.max), label); else EditorGUI.LabelField(position, label.text, "Use Range with float or int."); } }
请注意,出于性能原因,EditorGUILayout 函数不能与属性绘制器一起使用。
注意: 列表和数组在自定义绘制器中的处理方式不同。当 SerializedProperty
被传递给 CreatePropertyGUI
方法时,它表示列表中的每个项目。但是,当需要对列表本身进行自定义绘制时,您必须对属性进行相应的包装。
如果您需要属性绘制器执行清理任务,例如从编辑器事件中分离,您可以实现 IDisposable 接口。此接口允许您定义一个将在编辑器被销毁时调用的方法,使您有机会处理任何必要的清理操作。
其他资源: PropertyAttribute 类,CustomPropertyDrawer 类。
attribute | 属性的 PropertyAttribute。不适用于自定义类绘制器。(只读) |
fieldInfo | 此属性表示的成员的反射 FieldInfo。(只读) |
preferredLabel | 此属性的标签。(只读) |
CreatePropertyGUI | 使用 UI Toolkit 为属性创建自定义 GUI。 |
GetPropertyHeight | 重写此方法以指定此字段的 GUI 高度(以像素为单位)。 |
OnGUI | 重写此方法以创建您自己的基于 IMGUI 的属性 GUI。 |