- Home /
PropertyDrawer for list. Subclass item fields not showing. Help needed!
Confusing title, I know... Here's what I mean: I'm trying to draw a list of objects of the class Action
. Every element in the list is a different type of subclass of Action (for example Idle
, Leap
or Wait
). The problem is that I want to modify the specific values of these subclasses but their fields are currently not showing up when I use the Editor.propertyField(property: action, includeChildren: true)
.
This is what I want: I think the reason for this problem is I'm using a List<Action>
for storing my subclasses. Thus the property automatically assumes all objects are of class Action (which only has one public variable "privateName") and not the subclasses Wait, Idle or Leap. Is there any way to draw all of the fields of the subclass when having them in a list? I'm currently using a Reorderable list to draw my actions and am currently calling Editor.propertyField(property: action, includeChildren: true)
on each and every one to display them. This is the code i use to create my list. public List<Action> actionList = new List<Action>();
Answer by Bunny83 · Oct 05, 2019 at 01:06 AM
Custom serializable classes do not support inheritance and polymorphism when it comes to serialization. Such classes are always serialized based on the field type. No type information is stored in the serialized data.
Please read this page carefully
CustomEditors or Property drawers can only change how the data is presented. You can not change the way how it's serialized.
There are ways to implement custom serialization logic by implementing the ISerializationCallbackReceiver interface. However it's only meant to convert things that can not be serialized into data that can be serialized.
I'm not very used to using unity answers so I had to make my comment into an "answer". I have clarified my question inside that answer, so please check it out. :)
Answer by ParkourTeddy · Oct 05, 2019 at 06:53 PM
@Bunny83 Thanks for your answer. Though it doesn't quite answer my question, but I don't think I made myself clear enough, so I'll try again. I will try to show you the behavior I want and what I am currently getting from a simple example.
TLDR can be found at the bottom.
Let's say we have a script QAction (The Q doesn't stand for anything, it's just there to not interfere with my other scripts).
[System.Serializable]
public class QAction
{
[HideInInspector]
public string actionName = "Empty Action";
public string privateName = "";
public virtual void Act()
{
Debug.Log("(" + actionName + ") Somebody called me!");
}
}
Then we have two subclasses of QAction. QWalk and QJump.
[System.Serializable]
public class QWalk : QAction
{
public float speed = 10f;
public float duration = 15f;
public QWalk()
{
actionName = "Walk";
}
public override void Act()
{
Debug.Log("(" + actionName + ") is walking!");
}
}
[System.Serializable]
public class QJump : QAction
{
public float jumpHeight = 5f;
public float airTime = 2f;
public QJump()
{
actionName = "Jump";
}
public override void Act()
{
Debug.Log("(" + actionName + ") Jumped!");
}
}
Now let's say I want a property drawer for QWalk (To keep this simple I'll just add some extra space below the property).
[CustomPropertyDrawer(typeof(QWalk))]
public class QWalkDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.PropertyField(position, property, includeChildren: true);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight * 7f;
}
}
Everything works as intended, I can see my property and there is some extra space below it (see picture below). Now let's say I want to make a nice propertydrawer for a list of actions, so I create a script for the QActionList and a QActionListDrawer.
[System.Serializable]
public class QActionList
{
public QAction[] actionList = new QAction[3] { new QWalk(), new QJump(), new QAction() };
}
[CustomPropertyDrawer(typeof(QActionList))]
public class QActionListDrawer : PropertyDrawer
{
SerializedProperty list;
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
list = property.FindPropertyRelative("actionList");
EditorGUI.PropertyField(position, list, includeChildren: true);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.singleLineHeight * 10f;
}
}
Let's also create a simple container where we can view how the different drawers look.
public class QTestContainer : MonoBehaviour
{
public QJump jumpAction;
public QWalk walkAction;
public QActionList actionList;
}
But when I view the script in the inspector I get the following...
We can see that the two first elements (Jump Action and Walk Action) are being drawn correctly, but in the list both the Jump and Walk actions are missing fields. Thus I cannot access the values of Walk and Jump because (I suspect) the array only draws the fields inherited from QAction. How can I fix this (or work around it) so that I easily can edit the different actions when inside an array or list?
TLDR: For some reason when drawing the actions using a list, Unity only draws the fields of the base class QAction instead of drawing them as in the picture above "Jump Action" and "Walk Action" which is the way I want them to be displayed inside of the array "Action List".
UA stuff: I've upvoted your question, hopefully that should give you enough karama to add comments. I don't think this this answer you've created CAN be properly converted to a comment (so I won't do it). But in the future, I'd suggest you edit/add to your Question, rather than create an "answer" with more question details.
What Bunny was talking about was this class's member, the array definition:
[System.Serializable]
public class QActionList
{
public QAction[] actionList = new QAction[3] { new QWalk(), new QJump(), new QAction() };
}
Even though you instantiated all those elements as QWalk or QJump, they will only be SERIALIZED as QActions. $$anonymous$$eaning, only the fields in the QAction class will be serialized. Therefore you will NOT have the data specific to QWalk available in your SerializedProperty when drawn.
To be serialized properly, you must declare, in your QActionList class, an array of EACH type to be serialized (like your counter-example). THEN, you can use the ISerialization callbacks, to convert/merge this into a single (not-serializable) list of QActions for runtime use (and back to multiple lists for serialization). The QActionList property drawer will , I suspect, need custom code to "fake" the multiple arrays into looking like a single list.
Thank you! This clarified everything a lot! :)