Custom attributes are a lifesaver for someone like me, who spends most of their time beautifying inspectors.
With the ability to create customized inspectors using custom attributes, developers can save significant time and effort that would otherwise be spent writing custom editors for their scripts. They can help to simplify the development process by reducing the amount of boilerplate code that developers need to write.
What are attributes?
Attributes are markers that can be placed above a class, property, or function in a script to indicate special behavior.
Unity provides us with some default built-in attributes which most of us might be aware of!!
[Header] - Helps organize variables on our inspector.
[Range(min,max)] - Give us a slider with min and max limits - restricting assignment between 2 values
[SerializedField] - exposed variables to inpsector
[Tooltip("Description")] - gives description of variables in the inspector.
[TextArea(minLines: , maxLines: )] - Applied to string to give a larger text area to fill in
Code:
[Header("This is default Unity Header")]
public int testV1;
[Range(0, 20)]
public int unityDefaultRangeVariable;
[Tooltip("This is UnityDefault Tool Tip")]
public int unityDefaultToolTip;
[TextArea(5, 10)]
public string unityDefaultTextArea;
Result:
While Unity provides a set of pre-defined attributes, there may be instances where developers need to define their own custom attributes to suit their specific needs. In such cases, custom property attributes and property drawers can be a game-changer.
Custom property attributes allow developers to define their own attributes and associate them with specific fields or properties within their scripts. This enables developers to add additional metadata and functionality to their scripts beyond what is offered by Unity's pre-defined attributes.
Property drawers, on the other hand, are used to customize the way in which a specific property or field is displayed within the Inspector window. By creating custom property drawers, developers can create a more intuitive and user-friendly interface for modifying properties in their scripts.
Property Drawers can be used to customize the look of certain controls in the Inspector window by using attributes on your scripts or by controlling how a specific Serializable class should look.
Property attributes in Unity are used to add metadata to properties (fields) in Unity components. They can be used to control how properties are displayed and edited in the Unity editor, and provide additional functionality to the property.
Lets start with a simple ReadOnly Custom Attribute which makes a public/serialized field non-editable in the unity inspector.
So to create a [ReadOnly] custom attribute all we need is 2 classes:
ReadOnlyPropertyAttribute
ReadOnlyPropertyDrawer
When creating an Attribute, the class name must have the suffix “Attribute” on the end - ReadOnlyPropertyAttribute. This is how C# and Unity know to treat this as an Attribute.
ReadOnly Property Attribute and Drawer
Property Attribute:
using UnityEngine;
using System;
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true , Inherited = true)]
public class ReadOnlyPropertAttribute : PropertyAttribute
{
}
The [AttributeUsage] attribute is used to specify how a custom attribute can be used in C#. It is applied to the custom attribute class itself.
The [AttributeUsage] attribute is used to specify how a custom attribute can be used in C#. It is applied to the custom attribute class itself.
Here's a breakdown of the parameters used in [AttributeUsage]:
AttributeTargets: Specifies the targets that the attribute can be applied to. In this case, AttributeTargets.Field means that the attribute can only be applied to fields (properties) in classes.
AllowMultiple: Specifies whether the attribute can be applied multiple times to the same target. Setting AllowMultiple to true means that the attribute can be applied more than once to the same field.
Inherited: Specifies whether the attribute can be inherited by derived classes. Setting Inherited to true means that the attribute will be inherited by any derived classes of the class that the attribute is applied to.
ReadOnlyPropertyDrawer:
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(ReadOnlyPropertyAttribute))]
public class ReadOnlyPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label);
GUI.enabled = true;
}
}
Code:
[ReadOnlyProperty] public float readOnlyField;
Result:
As mentioned, ReadOnlyPropertyDrawer class can be used to customise the look on the inspector.
Class should extend PropertyDrawer, and [CustomPropertyDrawer(typeof(ReadOnlyPropertyAttribute))] is applied to a custom property drawer class and specifies that this property drawer should be used for any property that has the ReadOnlyPropertyAttribute attribute applied to it.
The OnGUI method in a Unity PropertyDrawer class is responsible for drawing the custom editor for a specific field or property. It is called by Unity's Editor system when the field or property is displayed in the Inspector window.
In the code you provided, the OnGUI method is overridden to create a custom property drawer for a specific property. The method takes three parameters:
position: The position of the property in the Inspector window.
property: The serialized property that is being drawn.
label: The label for the property.
The first line of the OnGUI method sets the GUI.enabled flag to false. This disables the property field so that it cannot be edited in the Inspector window.
The second line uses the EditorGUI.PropertyField method to draw the property field in the Inspector window. This method takes three parameters:
position: The position of the property field in the Inspector window.
property: The serialized property that is being drawn.
label: The label for the property.
By calling this method, the custom property drawer displays the property field in the Inspector window, just like the default property drawer would.
The third line sets the GUI.enabled flag back to true. This re-enables the property field, so that it can be selected and copied in the Inspector window.
Overall, this custom property drawer is used to display a read-only version of a property in the Inspector window. It does this by disabling the property field and then drawing it using the default property drawer. This prevents the property from being edited, while still allowing the user to view the value of the property.
InGameTags Property Attribute and Drawer
Lets write an other custom attribute for string, which displays all inGame tags as a drop down.
public string tagToHit;
All we have to do assign a tag name to the above variable. Manually writing down the tag name for this field in the inspector is prone to errors. So lets write a custom inspector, to display all in game tabs, where we can chose tag of our choice.
PropertyAttribute:
using UnityEngine;
using System;
[AttributeUsage( AttributeTargets.Field)]
public class InGameTagsAttribute : PropertyAttribute
{
}
PropertyField:
using UnityEngine;
using UnityEditor;
[CustomPropertyDrawer(typeof(InGameTagsAttribute))]
public class InGameTagsPropertyDrawers : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.String)
{
property.stringValue = EditorGUI.TagField(position, property.displayName, property.stringValue);
}
else
{
EditorGUI.PropertyField(position, property, label);
}
}
}
Code:
[InGameTags] public string inGameTags;
Result:
Code checks if the property being drawn is a string property, and if it is, displays a dropdown list of all the in-game tags in Unity to allow the user to select a tag for the property. If the property is not a string property, it is displayed as usual.
If the property being drawn is a string property, this displays a dropdown list of all the in-game tags in Unity using the EditorGUI.TagField() method. The position parameter specifies the position and size of the dropdown list, the property.displayName parameter specifies the label for the dropdown list, and the property.stringValue parameter specifies the current value of the string property.
InGameScenes Property Attribute and Drawer
Here lets display all the scenes as a dropdown for the user to chose. Similar to tags, this attribute can also be applied to string property.
PropertyAttribute:
using System;
using UnityEngine;
[AttributeUsage(AttributeTargets.Field)]
public class InGameScenesAttribute : PropertyAttribute
{
}
PropertyDrawer:
using System.IO;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(InGameScenesAttribute))]
public class InGameScenesPropertyDrawer : PropertyDrawer
{
int selectedIndex = 0;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.String)
{
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
//Get all scene names in the project
string[] inGameScenes = new string[scenes.Length];
for (int i = 0; i < scenes.Length; i++)
{
inGameScenes[i] = Path.GetFileNameWithoutExtension(scenes[i].path);
}
Rect updatedPosition = EditorGUI.PrefixLabel(position, label);
selectedIndex = EditorGUI.Popup(updatedPosition, selectedIndex, inGameScenes);
}
else
{
EditorGUI.PropertyField(position, property, label);
}
}
}
Code:
[InGameScenes] public string inGameTags;
Result:
Similar to above attribute, we apply this only to string. Firstly we get all scenes in game using EditorBuildSettings.scenes and convert this scene names(string array).
The EditorGUI.PrefixLabel() method creates a label in front of a control, and returns the Rect that represents the remaining space that can be used to display the control. This is useful when you want to display a label for a control and ensure that the control is aligned with other controls in the same group. We call EditorGUI.Popup(updatedPosition, selectedIndex, inGameScenes) to display the popup control in the remaining space. The updatedPosition argument represents the area where the popup control should be displayed, and the selectedIndex and inGameScenes arguments represent the index of the selected item and the list of items to display in the popup control, respectively.
Password Field custom attribute and drawer
PropertyAttribute:
using System;
using UnityEngine;
[AttributeUsage(AttributeTargets.Field)]
public class PasswordCustomAttribute : PropertyAttribute
{
}
Property Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(PasswordCustomAttribute))]
public class PasswordCustomDrawer : PropertyDrawer
{
public bool showPassword = false;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.String)
{
if (!showPassword)
property.stringValue = EditorGUI.PasswordField(new
Rect(position.x, position.y, position.width - 10f,
position.height), label, property.stringValue);
else
EditorGUI.PropertyField(new Rect(position.x, position.y, position.width - 10f, position.height), property, label);
showPassword = EditorGUI.Toggle(new Rect(position.xMax - 10f, position.y, position.width, position.height), showPassword);
}
else
{
EditorGUI.PropertyField(position, property, label);
}
}
}
Code:
[PasswordCustom] public string passwordField;
Result:
If the showPassword boolean is false, it displays a PasswordField using EditorGUI with the given position, label and the current stringValue of the property. If showPassword is true, it displays the actual property field using EditorGUI.PropertyField.
The showPassword boolean is toggled using a Toggle field that is displayed at the right end of the same line as the property field, by subtracting 10f from the width of the property field's Rect to make room for the toggle.
Required Field Custom Attribute and Drawer
Property Attribute:
using System;
using UnityEngine;
[AttributeUsage(AttributeTargets.Field)]
public class RequiredAttribute : PropertyAttribute
{
}
Property Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(RequiredAttribute))]
public class RequiredPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
//Defining label field
var labelWidth = EditorGUIUtility.labelWidth;
var labelRect = new Rect(position.x, position.y, labelWidth, 19f);
GUI.Label(labelRect, label);
//if the field does have any reference values, make it red and add help box!!
if (property.objectReferenceValue == null)
{
var helpBoxRect = new Rect(position.x, position.y + labelRect.height + EditorGUIUtility.standardVerticalSpacing, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.HelpBox(helpBoxRect, "This is a Required Field which cant be null", MessageType.Error);
GUI.color = Color.red;
}
//Defining the actual property field
var fieldRect = new Rect(position.x + labelWidth, position.y, position.width - labelWidth, EditorGUI.GetPropertyHeight(property));
EditorGUI.PropertyField(fieldRect, property, GUIContent.none);
//Turn it back to default color
GUI.color = Color.white;
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (property.objectReferenceValue == null)
{
return 40f;
}
return 19f;
}
}
Code:
[Required] public GameObject requiredField;
Result:
The Required attribute is used to ensure that certain properties are not null, and this custom property drawer will display an error message if the property is null.
The RequiredPropertyDrawer class inherits from the PropertyDrawer class, which is used to customize how properties are displayed in the Unity Editor. The OnGUI method is called to draw the property, and the GetPropertyHeight method is called to get the height of the property.
In the OnGUI method, the label for the property is first drawn using the EditorGUIUtility.labelWidth and GUI.Label methods. Then, if the property is null, an error message is displayed using the EditorGUI.HelpBox method, and the GUI.color is set to red to indicate the error. Finally, the actual property field is drawn using the EditorGUI.PropertyField method.
In the GetPropertyHeight method, the height of the property is determined based on whether or not it is null. If it is null, the height is set to 40f to accommodate the error message, otherwise it is set to 19f, the height of the label field.
Overall, this custom property drawer provides a convenient way to ensure that required properties are not null in Unity, and provides visual feedback to the user when an error occurs.
Min Max Slider Attribute And Drawer
Property Attribute:
public class MinMaxSliderAttribute : PropertyAttribute
{
public float min, max;
public MinMaxSliderAttribute(float min, float max)
{
this.min = min;
this.max = max;
}
}
The MinMaxSliderAttribute constructor takes two parameters min and max which set the minimum and maximum values of the slider.
This custom attribute can be useful for creating sliders that allow users to select a range of values instead of just a single value. For example, it could be used for selecting a range of color values or selecting a range of values for a gradient.
Property Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(MinMaxSliderAttribute))]
public class MinMaxSliderDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.Vector2)
{
var minMaxAttribute = (MinMaxSliderAttribute)attribute;
//Define label
var lableRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth, position.height);
EditorGUI.LabelField(lableRect, label);
var min = property.vector2Value.x;
var max = property.vector2Value.y;
//Defile a min value field
var minFloatFieldRect = new Rect(position.x + lableRect.width, position.y, 40f, position.height);
min = EditorGUI.FloatField(minFloatFieldRect, min);
//Slider propery field
var sliderRect = new Rect(position.x + lableRect.width + 40f, position.y, position.width - lableRect.width - 80f - 5f, position.height);
EditorGUI.MinMaxSlider(sliderRect, ref min, ref max, minMaxAttribute.min, minMaxAttribute.max);
//Defile a min value field
var maxFloatFieldRect = new Rect(position.x + lableRect.width + 40f + sliderRect.width, position.y, 40f, position.height);
max = EditorGUI.FloatField(maxFloatFieldRect, max);
//Update value imediately
property.vector2Value = new Vector2(min, max);
}
else
{
EditorGUI.LabelField(position, label.text, "This Attribute only works with Vector2");
}
}
}
Code:
[MinMaxSlider(10, 20)] public Vector2 minMaxSlider;
Result:
The first thing the OnGUI method does is to cast the attribute field to MinMaxSliderAttribute to get the attribute that was applied to the property. This allows the method to get the minimum and maximum values for the slider.
Next, the method calculates the current min and max values from the SerializedProperty object. The vector2Value of the property is used to store the current min and max values.
Then, the method draws the label for the property using EditorGUI.LabelField. The label is drawn at the left edge of the property's position and has a width of EditorGUIUtility.labelWidth, which is a constant value that determines the default width of labels in the inspector.
After drawing the label, the method draws the slider using EditorGUI.MinMaxSlider. This method takes a Rect that defines the position and size of the slider, as well as the current min and max values and the minimum and maximum values allowed for the slider.
The MinMaxSlider method returns the new min and max values of the slider, which the OnGUI method uses to update the vector2Value of the SerializedProperty object.
Finally, the method draws two float fields for the min and max values of the property using EditorGUI.FloatField. These fields are positioned to the right of the label and slider, and they allow the user to manually enter the min and max values of the property if they don't want to use the slider.
Custom Progress Bar Attribute and Drawer
Property Attribute:
using UnityEngine;
public class ProgressBarAttribute : PropertyAttribute
{
public readonly float minValue;
public readonly float maxValue;
public ProgressBarAttribute(float minValue, float maxValue)
{
this.minValue = minValue;
this.maxValue = maxValue;
}
}
Property Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(ProgressBarAttribute))]
public class ProgressbarDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.Float)
{
//Get attribute variables
ProgressBarAttribute attributeRef = (ProgressBarAttribute)attribute;
var minValue = attributeRef.minValue;
var maxValue = attributeRef.maxValue;
var percentageProgress = ((property.floatValue - minValue) / (maxValue));
//Diplaying label field!
var labelFieldRect = new Rect(position.x, position.y, EditorGUIUtility.labelWidth, position.height);
EditorGUI.LabelField(labelFieldRect, label);
//BG for progress bar
var totalProgressBarWidth = position.width - labelFieldRect.width;
var unFilledProgressBarRect = new Rect(position.x + labelFieldRect.width, position.y, totalProgressBarWidth, position.height);
EditorGUI.DrawRect(unFilledProgressBarRect, Color.white);
var filledProgressBarRect = new Rect(position.x + labelFieldRect.width, position.y, totalProgressBarWidth * percentageProgress, position.height);
EditorGUI.DrawRect(filledProgressBarRect, Color.red);
GUI.color = Color.black;
EditorGUI.LabelField(new Rect(position.x + labelFieldRect.width, position.y, position.width, position.height),(percentageProgress * 100) + "%");
GUI.color = Color.white;
}
else
{
EditorGUI.LabelField(position, label.text, "This Attribute can only be used with int/float");
}
}
}
Code:
[ProgressBar(0, 100)] public float customProgressBar;
Result:
This is a custom property drawer for a "ProgressBarAttribute" that can be used on float properties in Unity's editor. The attribute specifies a minimum and maximum value for the property, and the drawer displays a progress bar that fills proportionally to the property's value relative to the minimum and maximum.
In the OnGUI method, the code checks if the property is of type float and gets the minimum and maximum values from the attribute. It then calculates the percentage progress by subtracting the minimum value from the property value and dividing by the range between the minimum and maximum.
Next, it draws the label field and the background of the progress bar. It calculates the filled progress bar rectangle based on the percentage progress and draws it in red using the EditorGUI.DrawRect method.
Finally, it sets the GUI color to black and draws the progress percentage as a label field on top of the progress bar, using the EditorGUI.LabelField method. It then resets the GUI color to white.
If the property is not of type float, the code simply displays a label field indicating that the attribute can only be used with int or float properties.
Enable If Custom Attribute and Drawer
Property attribute:
using UnityEngine;
public class EnableIfAttribute : PropertyAttribute
{
public readonly string boolName;
public EnableIfAttribute(string boolName)
{
this.boolName = boolName;
}
}
Property Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(EnableIfAttribute))]
public class EnableIfDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attributeRef = attribute as EnableIfAttribute;
bool shouldEnable = property.serializedObject.FindProperty(attributeRef.boolName).boolValue;
if(!shouldEnable)
{
EditorGUI.PropertyField(position, property, label);
}
else
{
GUI.enabled = false;
EditorGUI.PropertyField(position, property, label);
GUI.enabled = true;
}
}
}
Code:
public bool boolToEnable;
[EnableIf("boolToEnable")] public GameObject testEnable1;
[EnableIf("boolToEnable")] public int testEnable2;
[EnableIf("boolToEnable")] public float testEnable3;
[EnableIf("boolToEnable")] public Vector3 testEnable4;
[EnableIf("boolToEnable")] public Color testEnable5;
Result:
The EnableIfAttribute attribute is used to conditionally enable/disable a serialized property in the Unity Editor, based on the value of a boolean field.
The OnGUI method is responsible for rendering the property in the editor. The method first checks whether the boolean field specified in the EnableIfAttribute is true or false using the FindProperty method of the SerializedObject class. If the boolean field is false, the property is rendered normally using the PropertyField method of the EditorGUI class. If the boolean field is true, the property is rendered disabled by setting GUI.enabled to false before rendering and resetting it to true after rendering.
Hide If Custom Attribute and Drawer
Property attribute:
using UnityEngine;
public class HideIfAttribute : PropertyAttribute
{
public readonly string boolName;
public HideIfAttribute(string boolName)
{
this.boolName = boolName;
}
}
Property Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(HideIfAttribute))]
public class HideIfDrawer : PropertyDrawer
{
private bool enabled = false;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var attributeRef = attribute as HideIfAttribute;
bool shouldEnable = property.serializedObject.FindProperty(attributeRef.boolName).boolValue;
enabled = shouldEnable;
if (!shouldEnable)
{
EditorGUI.PropertyField(position, property, label);
}
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
if (enabled)
return 0;
return base.GetPropertyHeight(property, label);
}
}
Code:
public bool boolToHide;
[HideIf("boolToHide")] public GameObject testHideField1;
[HideIf("boolToHide")] public int testHideField2;
[HideIf("boolToHide")] public float testHideField3;
[HideIf("boolToHide")] public Vector3 testHideField4;
[HideIf("boolToHide")] public Color testHideField5;
Result:
This attribute is used to hide a property in the inspector based on the value of a boolean field.
In the OnGUI() method, we retrieve the value of the boolean field specified in the attribute using property.serializedObject.FindProperty(attributeRef.boolName).boolValue. If the value is false, we draw the property as usual using EditorGUI.PropertyField(position, property, label).
In the GetPropertyHeight() method, we check if the property should be hidden (enabled is true) and if so, we return a height of 0 to hide the property. Otherwise, we return the base height of the property using base.GetPropertyHeight(property, label).
Custom Info Box Attribute and Drawer
Property Attribute:
using UnityEditor;
using UnityEngine;
public class InfoBoxAttribute : PropertyAttribute
{
public readonly string info;
public readonly MessageType messageType;
public InfoBoxAttribute(string info, MessageType messageType)
{
this.info = info;
this.messageType = messageType;
}
}
Decorative Drawer:
Here to display info box, we will be using decorative drawer instead of property drawer which we have been using.
A DecoratorDrawer is similar to a PropertyDrawer, except that it doesn't draw a property but rather draws decorative elements based purely on the data it gets from its corresponding PropertyAttribute.
Unity uses builtin DecoratorDrawers for the SpaceAttribute and HeaderAttribute. You can also create your own DecoratorDrawers with matching PropertyAttributes.
Although a DecoratorDrawer conceptually is not meant to be associated with a specific field, its attribute still needs to be placed above a field in the script. However, unlike PropertyDrawer attributes, there can be multiple DecoratorDrawers attributes above the same field. Also unlike PropertyDrawers, if a DecoratorDrawer attribute is placed above a field that is a List or an array, the decorator will only show up once before the array; not for every array element.
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(InfoBoxAttribute))]
public class InfoBoxDrawer : DecoratorDrawer
{
public override void OnGUI(Rect position)
{
//get values
var attributeRef = (InfoBoxAttribute)attribute;
EditorGUI.HelpBox(new Rect(position.x, position.y + 10f, position.width, 30f), attributeRef.info, attributeRef.messageType);
}
public override float GetHeight()
{
return base.GetHeight() + 25f;
}
}
Code:
[InfoBox("This is an info", UnityEditor.MessageType.Warning)]
[SerializeField] public int helpInfoString;
Result:
InfoBoxAttribute is a custom attribute that can be added to fields or properties in Unity scripts to provide additional information about that particular field or property. The InfoBoxDrawer class extends from the DecoratorDrawer class, which means it will be drawn on top of the property it decorates.
The OnGUI method of the InfoBoxDrawer class is responsible for drawing the attribute in the Unity inspector. It first gets the value of the info and messageType properties from the InfoBoxAttribute. It then calls EditorGUI.HelpBox to draw a help box with the given message and message type.
The GetHeight method is used to determine the height of the property drawer. In this case, it returns the base height plus 25 pixels to provide some padding around the help box.
Custom Header Property Attribute and Drawer
Property Attribute:
using UnityEngine;
public class CustomHeaderAttribute : PropertyAttribute
{
public readonly string headerMessage;
public readonly float headerGap;
public CustomHeaderAttribute(string headerMessage, float headerGap = 10f)
{
this.headerMessage = headerMessage;
this.headerGap = headerGap;
}
}
Decorative Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(CustomHeaderAttribute))]
public class CustomHeaderDrawer : DecoratorDrawer
{
private const float HEADER_HEIGHT = 20f;
private float headerGap;
public override void OnGUI(Rect position)
{
//Get Values
var attributeRef = (CustomHeaderAttribute)attribute;
Rect startRect = new Rect(position.x, position.y + attributeRef.headerGap, position.width, HEADER_HEIGHT);
headerGap = attributeRef.headerGap;
EditorGUI.DrawRect(startRect, Color.white);
GUIStyle headerStyle = new GUIStyle();
headerStyle.alignment = TextAnchor.MiddleCenter;
headerStyle.fontStyle = FontStyle.Bold;
GUI.color = Color.white;
EditorGUI.LabelField(startRect, attributeRef.headerMessage, headerStyle);
}
public override float GetHeight()
{
return base.GetHeight() + headerGap + 5f;
}
}
Code:
[CustomHeader("This is a custom Header", 30f)]
[SerializeField] public int fieldAfterCustomHeader1;
[SerializeField] public int fieldAfterCustomHeader2;
[SerializeField] public int fieldAfterCustomHeader3;
[SerializeField] public int fieldAfterCustomHeader4;
[SerializeField] public int fieldAfterCustomHeader5;
[SerializeField] public int fieldAfterCustomHeader6;
[CustomHeader("This is a custom Header", 30f)]
[SerializeField] public int fieldAfterCustomHeader7;
[SerializeField] public int fieldAfterCustomHeader8;
[SerializeField] public int fieldAfterCustomHeader9;
[SerializeField] public int fieldAfterCustomHeader10;
Result:
The CustomHeaderAttribute is a custom attribute that can be used to add a custom header to a serialized field in the Unity Editor.
The OnGUI method of the CustomHeaderDrawer class is called by the Unity Editor when it is time to draw the custom header for the serialized field. The method first gets the CustomHeaderAttribute reference for the serialized field and then creates a Rect for the header based on the position of the serialized field and the headerGap value specified in the attribute.
The method then draws a white rectangle using the DrawRect method of the EditorGUI class, which serves as the background for the header. It then creates a new GUIStyle object and sets its alignment to center and font style to bold. Finally, it draws the header message using the LabelField method of the EditorGUI class and the GUIStyle object.
The GetHeight method is used to calculate the height of the header. It returns the base height plus the headerGap value specified in the attribute plus an additional 5 units of vertical spacing.
Custom Preview Display - Attribute and Property Drawer
Property Attribute:
using UnityEngine;
public class DisplayAttribute : PropertyAttribute
{
}
Property Drawer:
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(DisplayAttribute))]
public class DisplayCustomDrawer : PropertyDrawer
{
private const float PreviewHeight = 64f;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
// Draw the label
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
// Calculate the preview rect
Rect previewRect = new Rect(position.x, position.y, PreviewHeight, PreviewHeight);
// Draw the preview
Texture2D previewTexture = AssetPreview.GetAssetPreview(property.objectReferenceValue);
if (previewTexture != null)
{
EditorGUI.LabelField(previewRect, GUIContent.none, new GUIStyle(GUI.skin.box) { normal = { background = previewTexture } });
}
else
{
EditorGUI.LabelField(previewRect, "No preview available");
}
// Draw the object field
position.x += PreviewHeight + 4;
position.width -= PreviewHeight + 4;
EditorGUI.PropertyField(position, property, GUIContent.none);
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return Mathf.Max(base.GetPropertyHeight(property, label), PreviewHeight + EditorGUIUtility.standardVerticalSpacing);
}
}
Result:
The AssetPreview.GetAssetPreview method is called to get a preview texture of the SerializedProperty object reference value. If a preview texture is available, it is drawn using EditorGUI.LabelField with a new GUIStyle set to a Box with the texture as its background. If a preview texture is not available, a "No preview available" message is displayed instead.
ความคิดเห็น