Sometimes you have custom objects that contain data but do not derive from MonoBehaviour. Adding these objects as a field in a class that is MonoBehaviour will have no visual effect unless you write your own custom property drawer for the object's type.
Below is a simple example of a custom object, added to MonoBehaviour, and a custom property drawer for the custom object.
public enum Gender {
Male,
Female,
Other
}
// Needs the Serializable attribute otherwise the CustomPropertyDrawer wont be used
[Serializable]
public class UserInfo {
public string Name;
public int Age;
public Gender Gender;
}
// The class that you can attach to a GameObject
public class PropertyDrawerExample : MonoBehaviour {
public UserInfo UInfo;
}
[CustomPropertyDrawer( typeof( UserInfo ) )]
public class UserInfoDrawer : PropertyDrawer {
public override float GetPropertyHeight( SerializedProperty property, GUIContent label ) {
// The 6 comes from extra spacing between the fields (2px each)
return EditorGUIUtility.singleLineHeight * 4 + 6;
}
public override void OnGUI( Rect position, SerializedProperty property, GUIContent label ) {
EditorGUI.BeginProperty( position, label, property );
EditorGUI.LabelField( position, label );
var nameRect = new Rect( position.x, position.y + 18, position.width, 16 );
var ageRect = new Rect( position.x, position.y + 36, position.width, 16 );
var genderRect = new Rect( position.x, position.y + 54, position.width, 16 );
EditorGUI.indentLevel++;
EditorGUI.PropertyField( nameRect, property.FindPropertyRelative( "Name" ) );
EditorGUI.PropertyField( ageRect, property.FindPropertyRelative( "Age" ) );
EditorGUI.PropertyField( genderRect, property.FindPropertyRelative( "Gender" ) );
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
}
}
First off we define the custom object with all it's requirements. Just a simple class describing a user. This class is used in our PropertyDrawerExample class which we can add to a GameObject.
public enum Gender {
Male,
Female,
Other
}
[Serializable]
public class UserInfo {
public string Name;
public int Age;
public Gender Gender;
}
public class PropertyDrawerExample : MonoBehaviour {
public UserInfo UInfo;
}
The custom class needs the Serializable attribute otherwise the CustomPropertyDrawer will not be used
Next up is the CustomPropertyDrawer
First we have to define a class that derives from PropertyDrawer. The class definition also needs the CustomPropertyDrawer attribute. The parameter passed is the type of the object you want this drawer to be used for.
[CustomPropertyDrawer( typeof( UserInfo ) )]
public class UserInfoDrawer : PropertyDrawer {
Next we override the GetPropertyHeight function. This allows us to define a custom height for our property. In this case we know that our property will have four parts: label, name, age, and gender. Therefore we use EditorGUIUtility.singleLineHeight * 4, we add another 6 pixels because we want to space each field with two pixels in between.
public override float GetPropertyHeight( SerializedProperty property, GUIContent label ) {
return EditorGUIUtility.singleLineHeight * 4 + 6;
}
Next is the actual OnGUI method. We start it off with EditorGUI.BeginProperty([...]) and end the function with EditorGUI.EndProperty(). We do this so that if this property would be part of a prefab, the actual prefab overriding logic would work for everything in between those two methods.
public override void OnGUI( Rect position, SerializedProperty property, GUIContent label ) {
EditorGUI.BeginProperty( position, label, property );
After that we show a label containing the name of the field and we already define the rectangles for our fields.
EditorGUI.LabelField( position, label );
var nameRect = new Rect( position.x, position.y + 18, position.width, 16 );
var ageRect = new Rect( position.x, position.y + 36, position.width, 16 );
var genderRect = new Rect( position.x, position.y + 54, position.width, 16 );
Every field is spaced by 16 + 2 pixels and the height is 16 (which is the same as EditorGUIUtility.singleLineHeight)
Next we indent the UI with one tab for a bit nicer layout, display the properties, un-indent the GUI, and end with EditorGUI.EndProperty.
EditorGUI.indentLevel++;
EditorGUI.PropertyField( nameRect, property.FindPropertyRelative( "Name" ) );
EditorGUI.PropertyField( ageRect, property.FindPropertyRelative( "Age" ) );
EditorGUI.PropertyField( genderRect, property.FindPropertyRelative( "Gender" ) );
EditorGUI.indentLevel--;
EditorGUI.EndProperty();
We display the fields by using EditorGUI.PropertyField which requires a rectangle for the position and a SerializedProperty for the property to show. We acquire the property by calling FindPropertyRelative("...") on the property passed in the OnGUI function. Note that these are case-sensitive and non-public properties cannot be found!
For this example I am not saving the properties return from property.FindPropertyRelative("..."). You should save these in private fields in the class to prevent unnecessary calls
Result
Before
After