- Home /
How to assign a field within a PropertyDrawer?
Is it possible to access field variables and edit inside of a PropertyDrawer or is it just for properties?
Answer by SolidAlloy · Oct 19, 2020 at 09:12 PM
Perhaps, it is the bad naming, but the "Property" part in PropertyDrawer does not mean it is for properties. In fact, it is the opposite - PropertyDrawers are for serialized fields: private fields marked with the [SerializeField] attribute and public fields. PropertyDrawer cannot be used for a property because properties are not serialized by Unity.
PropertyDrawers are used to customize the look of serialized fields in the inspector. For each serialized field, Unity creates a SerializedProperty object (not to confuse with C# property). When you implement a PropertyDrawer class, Unity provides you with the SerializedProperty object, so that you can access the field value and draw the field yourself.
Why would we need SerializedProperty if we could access and change the field value directly? When you change the field value, it does not get automatically serialized (saved to a text file). You would need to save a new value to the asset each time it is changed. However, SerializedProperty does it for you. You just describe how you want the field to be displayed, and SerializedProperty takes care of the serialization part.
I tried to create a simple yet meaningful example of how you can implement a custom PropertyDrawer. Let's say we have a Player MonoBehaviour that contains a PlayerHealth field:
public class Player : MonoBehaviour
{
[SerializeField] private PlayerHealth _health;
}
[Serializable]
public class PlayerHealth
{
public int MaxHealth = 100;
public int CurrentHealth = 100;
}
You don't want users to assign a current health value higher than the max health. You can create a slider with [Range(0, 100)] but max health can be changed too. Then, it would be natural to create PropertyDrawer like this:
[CustomPropertyDrawer(typeof(PlayerHealth))]
public class PlayerHealthDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
// Draw the MaxHealth field.
SerializedProperty maxHealthProperty = property.FindPropertyRelative(nameof(PlayerHealth.MaxHealth));
EditorGUILayout.DelayedIntField(maxHealthProperty);
SerializedProperty currentHealthProperty = property.FindPropertyRelative(nameof(PlayerHealth.CurrentHealth));
// If the MaxHealth value was changed, check that CurrentHealth is not higher.
if (currentHealthProperty.intValue > maxHealthProperty.intValue)
currentHealthProperty.intValue = maxHealthProperty.intValue;
// Draw the CurrentHealth field as a slider, where max value is MaxHealth.
EditorGUILayout.IntSlider(currentHealthProperty, 0, maxHealthProperty.intValue);
EditorGUI.EndProperty();
}
}
And now you have a slider field with a variable max value:
I noticed I didn't answer your question directly. How can you assign a value to a field? First, you need to find the field by its name. If the field is public, I recommend you use nameof(ClassName.FieldName) as I did above. When the SerializedProperty bound to the field is found, you can assign a value to the field via SerializedProperty.intValue (or boolValue, or other [name]Value; SerializedProperty has a bunch of these). However, most of the times, you don't have to do this. You just draw the field (e.g. EditorGUILayout.DelayedIntField(maxHealthProperty)), and when you change the value in the inspector, it will automatically change in the class field.
But what if the field I'm trying to access and edit is a delegate or custom class object?
Custom class object
Such a class needs to have the Serializable attribute. You can leave it up to Unity to draw the class in the inspector. It will just draw its fields in a foldout like it would for $$anonymous$$onoBehaviour fields.
public class TestBehaviour : $$anonymous$$onoBehaviour
{
[SerializeField] private SerializableParent _parentClass;
}
[Serializable]
public class SerializableParent
{
[SerializeField] private SerializableChild _childClass;
}
[Serializable]
public class SerializableChild
{
[SerializeField] private string _firstField;
[SerializeField] private int _secondField;
}
[CustomPropertyDrawer(typeof(SerializableParent))]
public class SerializableParentDrawer : PropertyDrawer
{
EditorGUI.BeginProperty(position, label, property);
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
SerializedProperty serializableChild = property.FindPropertyRelative(nameof("_childClass");
EditorGUILayout.PropertyField(serializableChild); // This will just render a foldout with a string field and an int field.
}
EditorGUI.EndProperty();
}
If you need to access/customize the SerializableChild fields, you can access them by path:
SerializedProperty firstField = property.FindPropertyRelative("_childClass._firstField");
// Draw firstField or access its value.
If you need to customize the look of SerializableChild, I advise that you create a separate PropertyDrawer for it. Then, when you use EditorGUILayout.PropertyField(serializableChild) in the SerializableParent PropertyDrawer, it will automatically find the custom SerializableChild PropertyDrawer you created earlier and will draw its fields according to that drawer.
Your answer
Follow this Question
Related Questions
Custom PropertyDrawer in Array. Problem with expanding. 0 Answers
Customizing Inspector variables. 2 Answers
How do I implement Draggable Properties with custom Labels in Editor or PropertyDrawer? 1 Answer
exclude public field from inspector 2 Answers
How To Draw a PropertyDrawer within a PropertyDrawer 0 Answers