Search Unity

  1. Get all the Unite Berlin 2018 news on the blog.
    Dismiss Notice
  2. Unity 2018.2 has arrived! Read about it here.
    Dismiss Notice
  3. We're looking for your feedback on the platforms you use and how you use them. Let us know!
    Dismiss Notice
  4. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  5. Improve your Unity skills with a certified instructor in a private, interactive classroom. Learn more.
    Dismiss Notice
  6. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  7. Magic Leap’s Lumin SDK Technical Preview for Unity lets you get started creating content for Magic Leap One™. Find more information on our blog!
    Dismiss Notice
  8. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Custom inspector with array of inherited types?

Discussion in 'Extensions & OnGUI' started by Yandalf, Aug 9, 2018 at 3:04 PM.

  1. Yandalf

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    17
    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. }
    5.  
    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;
    6.  
    7.     private void OnEnable()
    8.     {
    9.         m_myTarget = target as Species;
    10.         m_requirements = new List<Requirement(m_myTarget.Requirements);
    11.     }
    12.  
    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

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    17
    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

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    274
    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

    Yandalf

    Joined:
    Feb 11, 2014
    Posts:
    17
    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

    Suduckgames

    Joined:
    Nov 28, 2016
    Posts:
    55
    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):
    1.  
    2.     public void OnEnable()
    3.     {
    4.        
    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