Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Custom inspector with array of inherited types?

Discussion in 'Immediate Mode GUI (IMGUI)' started by Yandalf, Aug 9, 2018.

  1. Yandalf


    Feb 11, 2014
    Hey everyone!

    I've been trying out some slightly trickier things with Editor scripting and have run into a snag.
    I've made a ScriptableObject called Species that contains an array of class EvoRequirement, and two inherited classes of EvoRequirement:
    Code (CSharp):
    1. public class Species : ScriptableObject
    2. {
    3.     public EvoRequirement[] Requirements = new EvoRequirement[0];
    4. }
    6. [Serializable]
    7. public class EvoRequirement { }
    8. public class EmptyRequirement : EvoRequirement { }
    9. public class StatRequirement: EvoRequirement { }
    Now I'm writing a custom inspector for Species, where I copy Requirements into a List for easier editing, and during OnDisable I give Requirements the content of the copied and edited list, after removing all null and EmptyRequirement elements:
    Code (CSharp):
    1. [CustomEditor(typeof(Species))]
    2. public class SpeciesEditor : Editor
    3. {
    4.     private Species m_myTarget;
    5.     private List<EvoRequirement> m_requirements;
    7.     private void OnEnable()
    8.     {
    9.         m_myTarget = target as Species;
    10.         m_requirements = new List<Requirement(m_myTarget.Requirements);
    11.     }
    13.     private void OnDisable()
    14.     {
    15.         if(m_myTarget != null && m_requirements != null)
    16.         {
    17.             for(int i = m_requirements.Count - 1; i >= 0; i--)
    18.             {
    19.                 if (m_requirements[i] == null ||
    20.                        m_requirements[i].GetType() == typeof(EmptyRequirement))
    21.                     m_requirements.RemoveAt(i);
    22.             }
    23.             m_myTarget.Requirements = m_requirements.ToArray();
    24.             m_requirements.Clear();
    25.         }
    26.     }
    27. }
    This all seems to work fine as intended, but when I recompile any part of my code (including classes that have nothing to do with this) and then inspect the SO again, the content of the array seems to get all set back to the base type.

    This is what shows in the inspector, I made it so base EvoRequirements would write "Unimplemented Requirement type!", while EmptyRequirements give a dropdown that when changed, changes that element to a new EvoRequirement of the specified type. StatRequirement draws the element with 3 enum popups.
    Null elements write a "Null element!" message.
    Code (CSharp):
    1.     private EvoRequirement DrawRequirement(EvoRequirement requirement)
    2.     {
    3.         if (requirement as EmptyRequirement != null)
    4.         {
    5.             EmptyRequirement req = (EmptyRequirement)requirement;
    6.             req.Type = (EmptyRequirement.RequirementType)EditorGUILayout.EnumPopup(req.Type);
    7.             if (req.Type == EmptyRequirement.RequirementType.Stat)
    8.             {
    9.                 return new StatRequirement();
    10.             }
    11.             return req;
    12.         }
    13.         else if (requirement as StatRequirement != null)
    14.         {
    15.             StatRequirement req = (StatRequirement)requirement;
    16.             EditorGUILayout.BeginHorizontal();
    17.             req.stat = (StatRequirement.Stat)EditorGUILayout.EnumPopup(req.stat);
    18.             req.comparison = (EvoRequirement.Comparison)EditorGUILayout.EnumPopup(req.comparison);
    19.             req.amount = EditorGUILayout.IntField(req.amount);
    20.             EditorGUILayout.EndHorizontal();
    21.             return req;
    22.         }
    23.         else if(requirement != null)
    24.         {
    25.             EditorGUILayout.LabelField("Unimplemented Requirement type!");
    26.             return requirement;
    27.         }
    28.         else
    29.         {
    30.             EditorGUILayout.LabelField("Null element!");
    31.             return null;
    32.         }
    33.     }
    After a recompile all elements will however turn into Unimplemented messages. It does remember the length of the array, but the individual data is lost.
    Is the Unity Serializer incapable of dealing with inherited types? How should I go about this instead?
    Thanks in advance!
  2. Yandalf


    Feb 11, 2014
    Disregard the question if Unity doesn't support inherited serialization. No, no it doesn't.

    I'm currently workin on a workaround by having Species have an array of each subtype of EvoRequirement, and only adding all these together in the inspector and splitting it back up on save.
    This works, but it does make the class slightly more bloated to use since each of those arrays needs to be public so I can edit it in the inspector.
    Is there actually a way to write an inspector for elements that are private with [SerializeField] attributes? As far as I'm aware you can't reach those, but I'd be very happy to hear I'm wrong there.
  3. brownboot67


    Jan 5, 2013
    I solve this by making the base class inherit ScriptableObject, for you this is EvoRequirement. Then, as you add new instances of the child classes (empty, stat, whatever) to your array create their assets and add them inside your main ScriptableObject class's asset (inside the Species asset).
  4. Yandalf


    Feb 11, 2014
    Hi brownboot67!

    Thanks for the suggestion. it's a solution I'm considering but it is gonna cause a wild growth of ScriptableObjects that I'd rather avoid.
    I read it's possible to access private serialized fields by using SerializedObjects and finding the properties instead, but since that doesn't give me access to types or allow me to change it as easily (for the EmptyRequirements to change to Stat and later Requirements) I'm not sure that'll fix it either.
  5. Suduckgames


    Nov 28, 2016
    To serialize lists of non-ScriptableObject subclasses, you'll need to implement ISerializationCallbackReceiver to handle your own serialization. But as brownboot suggested, you the best way is to inherit from ScriptableObject on EvoRequirement

    I think that what are you looking for is hideFlags = HideFlags.HideAndDontSave on the OnEnable() part of the object; This is due because of Unity reload assembly sytem as marked in

    to make it work just add on the ScriptableObject EvoRequieremnt extend from scriptableObject and add

    Code (CSharp):
    2.     public void OnEnable()
    3.     {
    5.         hideFlags = HideFlags.HideAndDontSave;
    6.     }
    You will see that when you enter/exit plays mode the data persist, however take care because in my case, I lost the data when I restart unity
  6. technostrife


    Apr 5, 2022
    AustinDrozin and mickeyband like this.