Search Unity

Hiding inherited public variable in the inspector?

Discussion in 'Scripting' started by virror, Dec 9, 2012.

  1. The_MAZZTer

    The_MAZZTer

    Joined:
    Sep 19, 2019
    Posts:
    3
    I love your solution DonLoquacious, however it looks like you forgot to set the "editorForChildClasses" flag in the CustomEditor constructor. It didn't work for me without that. (Or maybe that's a new flag since you made this solution.)

    Code (CSharp):
    1. [CustomEditor(typeof(MonoBehaviour), true)]
    Adding this in case anyone needs it like I did.

    Baste's solution is nice too, though it requires you to have the source for the base type.

    It also fails to work with private fields using SerializeFieldAttribute since GetField isn't looking for non-public members, and it additionally won't find them unless you drill down to the proper .BaseType first. It's not too tough to fix and I had that working with some other enhancements before I saw Don's solution.
     
    Skaster87 likes this.
  2. Toemsel92

    Toemsel92

    Joined:
    Oct 22, 2016
    Posts:
    7
    I did tune @DonLoquacious implementation, so that you are able to define "HideProperties" even in the base-class.

    Code (CSharp):
    1.     [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    2.     public class HidePropertiesAttribute : Attribute
    3.     {
    4.         public string[] PropertyNames;
    5.  
    6.         public HidePropertiesAttribute(params string[] propertyNames)
    7.         {
    8.             PropertyNames = propertyNames;
    9.         }
    10.     }
    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Reflection;
    6. using UnityEditor;
    7.  
    8. [CustomEditor(typeof(BaseBehaviour), editorForChildClasses:true)]
    9. public class MonoBehaviourEditorWithExcludedProperties : Editor
    10. {
    11.     private string[] propertiesToHide;
    12.  
    13.     private void OnEnable()
    14.     {
    15.         IEnumerable<Type> typeAndChildren = Assembly.GetAssembly(target.GetType()).GetTypes().Where(t => target.GetType().IsSubclassOf(t) || IsSubclassOfRawGeneric(target.GetType(), t));
    16.         var attrs = (HidePropertiesAttribute[])typeAndChildren.Select(t => t.GetCustomAttributes<HidePropertiesAttribute>(true)).SelectMany(t => t).ToArray();
    17.         propertiesToHide = attrs?.Select(a => a.PropertyNames).SelectMany(s => s).ToArray() ?? null;
    18.     }
    19.  
    20.     public override void OnInspectorGUI()
    21.     {
    22.         if (propertiesToHide != null)
    23.         {
    24.             serializedObject.Update();
    25.             EditorGUI.BeginChangeCheck();
    26.             DrawPropertiesExcluding(serializedObject, propertiesToHide);
    27.             if (EditorGUI.EndChangeCheck())
    28.                 serializedObject.ApplyModifiedProperties();
    29.         }
    30.         else
    31.         {
    32.             base.OnInspectorGUI();
    33.         }
    34.     }
    35.  
    36.     private static bool IsSubclassOfRawGeneric(Type child, Type parent)
    37.     {
    38.         if (child == parent)
    39.             return false;
    40.  
    41.         if (child.IsSubclassOf(parent))
    42.             return true;
    43.  
    44.         var parameters = parent.GetGenericArguments();
    45.         var isParameterLessGeneric = !(parameters != null && parameters.Length > 0 &&
    46.             ((parameters[0].Attributes & TypeAttributes.BeforeFieldInit) == TypeAttributes.BeforeFieldInit));
    47.  
    48.         while (child != null && child != typeof(object))
    49.         {
    50.             var cur = GetFullTypeDefinition(child);
    51.             if (parent == cur || (isParameterLessGeneric && cur.GetInterfaces().Select(i => GetFullTypeDefinition(i)).Contains(GetFullTypeDefinition(parent))))
    52.                 return true;
    53.             else if (!isParameterLessGeneric)
    54.                 if (GetFullTypeDefinition(parent) == cur && !cur.IsInterface)
    55.                 {
    56.                     if (VerifyGenericArguments(GetFullTypeDefinition(parent), cur))
    57.                         if (VerifyGenericArguments(parent, child))
    58.                             return true;
    59.                 }
    60.                 else
    61.                     foreach (var item in child.GetInterfaces().Where(i => GetFullTypeDefinition(parent) == GetFullTypeDefinition(i)))
    62.                         if (VerifyGenericArguments(parent, item))
    63.                             return true;
    64.  
    65.             child = child.BaseType;
    66.         }
    67.  
    68.         return false;
    69.     }
    70.  
    71.     private static Type GetFullTypeDefinition(Type type)
    72.     {
    73.         return type.IsGenericType ? type.GetGenericTypeDefinition() : type;
    74.     }
    75.  
    76.     private static bool VerifyGenericArguments(Type parent, Type child)
    77.     {
    78.         Type[] childArguments = child.GetGenericArguments();
    79.         Type[] parentArguments = parent.GetGenericArguments();
    80.         if (childArguments.Length == parentArguments.Length)
    81.             for (int i = 0; i < childArguments.Length; i++)
    82.                 if (childArguments[i].Assembly != parentArguments[i].Assembly || childArguments[i].Name != parentArguments[i].Name || childArguments[i].Namespace != parentArguments[i].Namespace)
    83.                     if (!childArguments[i].IsSubclassOf(parentArguments[i]))
    84.                         return false;
    85.  
    86.         return true;
    87.     }
    88. }
    89.  
    90. [code=CSharp]
    91. using Assets.Code;
    92. using Assets.Code.Attributes;
    93. using System;
    94. using System.Collections.Generic;
    95. using System.Linq;
    96. using System.Reflection;
    97. using UnityEditor;
    98.  
    99. [CustomEditor(typeof(BaseBehaviour), editorForChildClasses:true)]
    100. public class MonoBehaviourEditorWithExcludedProperties : Editor
    101. {
    102.     private string[] propertiesToHide;
    103.  
    104.     private void OnEnable()
    105.     {
    106.         IEnumerable<Type> typeAndChildren = Assembly.GetAssembly(target.GetType()).GetTypes().Where(t => target.GetType().IsSubclassOf(t) || IsSubclassOfRawGeneric(target.GetType(), t));
    107.         var attrs = (HidePropertiesAttribute[])typeAndChildren.Select(t => t.GetCustomAttributes<HidePropertiesAttribute>(true)).SelectMany(t => t).ToArray();
    108.         propertiesToHide = attrs?.Select(a => a.PropertyNames).SelectMany(s => s).ToArray() ?? null;
    109.     }
    110.  
    111.     public override void OnInspectorGUI()
    112.     {
    113.         if (propertiesToHide != null)
    114.         {
    115.             serializedObject.Update();
    116.             EditorGUI.BeginChangeCheck();
    117.             DrawPropertiesExcluding(serializedObject, propertiesToHide);
    118.             if (EditorGUI.EndChangeCheck())
    119.                 serializedObject.ApplyModifiedProperties();
    120.         }
    121.         else
    122.         {
    123.             base.OnInspectorGUI();
    124.         }
    125.     }
    126.  
    127.     private static bool IsSubclassOfRawGeneric(Type child, Type parent)
    128.     {
    129.         if (child == parent)
    130.             return false;
    131.  
    132.         if (child.IsSubclassOf(parent))
    133.             return true;
    134.  
    135.         var parameters = parent.GetGenericArguments();
    136.         var isParameterLessGeneric = !(parameters != null && parameters.Length > 0 &&
    137.             ((parameters[0].Attributes & TypeAttributes.BeforeFieldInit) == TypeAttributes.BeforeFieldInit));
    138.  
    139.         while (child != null && child != typeof(object))
    140.         {
    141.             var cur = GetFullTypeDefinition(child);
    142.             if (parent == cur || (isParameterLessGeneric && cur.GetInterfaces().Select(i => GetFullTypeDefinition(i)).Contains(GetFullTypeDefinition(parent))))
    143.                 return true;
    144.             else if (!isParameterLessGeneric)
    145.                 if (GetFullTypeDefinition(parent) == cur && !cur.IsInterface)
    146.                 {
    147.                     if (VerifyGenericArguments(GetFullTypeDefinition(parent), cur))
    148.                         if (VerifyGenericArguments(parent, child))
    149.                             return true;
    150.                 }
    151.                 else
    152.                     foreach (var item in child.GetInterfaces().Where(i => GetFullTypeDefinition(parent) == GetFullTypeDefinition(i)))
    153.                         if (VerifyGenericArguments(parent, item))
    154.                             return true;
    155.  
    156.             child = child.BaseType;
    157.         }
    158.  
    159.         return false;
    160.     }
    161.  
    162.     private static Type GetFullTypeDefinition(Type type)
    163.     {
    164.         return type.IsGenericType ? type.GetGenericTypeDefinition() : type;
    165.     }
    166.  
    167.     private static bool VerifyGenericArguments(Type parent, Type child)
    168.     {
    169.         Type[] childArguments = child.GetGenericArguments();
    170.         Type[] parentArguments = parent.GetGenericArguments();
    171.         if (childArguments.Length == parentArguments.Length)
    172.             for (int i = 0; i < childArguments.Length; i++)
    173.                 if (childArguments[i].Assembly != parentArguments[i].Assembly || childArguments[i].Name != parentArguments[i].Name || childArguments[i].Namespace != parentArguments[i].Namespace)
    174.                     if (!childArguments[i].IsSubclassOf(parentArguments[i]))
    175.                         return false;
    176.  
    177.         return true;
    178.     }
    179. }
    180.  
     
  3. TimLoo6

    TimLoo6

    Joined:
    Dec 29, 2019
    Posts:
    6
    I have the same issues
    Say I have my base class with a lot of functions each one has its own variable
    and I want the child to inherit only one function with the variables that the function needs
    The other variables need by other functions are useless to this child so I don't want to see them in the inspector
    that's what he meant
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    I would argue your design is faulty.

    Why would you have this class with N functions/variables, but child classes only need a single one.

    What's the point of the base class? What is the point of this inheritance? The fact they're polymorphic brings no benefit since the child class type shouldn't be treated as any of the N-1 members...

    ...

    Instead, what are you trying to accomplish? Maybe we can suggest a better design to your problem.
     
    Bunny83 and Suddoha like this.
  5. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    Add me to the list of people who have this question.

    Use case: An abstract base class with a bunch of fields and a large number of derived classes who share the basic functionality and expand upon it. A low number of exceptions that don't use one of the general fields, but use all of the others. The field to hide is not always the same.

    In a clean design, I would have to massively expand the number of classes I use and make everything inherit from some intermediate class. With combinatorical explosion, that could be a lot, and it would make everything so much more difficult to understand.

    The CLEAN solution here quite obviously is the ability to say "I don't use this one of the 20 generic fields" in the child classes. And subsequently hide it in the inspector.
     
    Nad_B likes this.
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,005
    Then it's still the case that your inheritance chain is misleading. Inheritance is an equivalence relationship. So any derived class IS also an instance of the base class. Whenever this does not hold true, your inheritance is flawed. You either may have an intermediate class that actually introduces that field / variable for all the classes that require that variable and have the other class that does not need it inherit from the actual base class. For example:

    Code (CSharp):
    1. public abstract class Base
    2. {
    3.     public float commonVar;
    4.     public abstract void SomeMethod();
    5. }
    6.  
    7. public abstract class BaseWithFoo : Base
    8. {
    9.     public float foo;
    10. }
    11.  
    12. public class ClassThatNeedsFoo : BaseWithFoo
    13. {
    14.     public void SomeMethod()
    15.     {
    16.         Debug.Log("Foo: " + foo);
    17.     }
    18. }
    19.  
    20. public class ClassWithoutFoo : Base
    21. {
    22.     public void SomeMethod()
    23.     {
    24.         Debug.Log("no foo here");
    25.     }
    26. }
    27.  
    A base class is always the lowest common denominator of the child classes. Every derived class IS also an instance of the base class. Whenever this doesn't hold true, you have a flawed design. That's why I'm with lordofduct with most what he said over the last 8 years in this thread ^^.

    Generally you can do whatever you want. Use goto's, use unsafe code, void pointers, whatever you want. However if you want to work with others or provide a solution that's meant as a framework that others should work with, you should stick to proper oop design and common design principles.
     
    angrypenguin likes this.
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,005
    That's not necessarily the case. If your classes are that different, you probably shouldn't use inheritance in the first place. What exact point would it be to have a common base class if 90% of the content of the base class does not hold true for a particular class? You would be better off with implementing interfaces. That way the actual fields are implemented in the concrete classes and you break the generally flawed inheritance relationship.

    I've done something like that with my SimpleJSON framework. The base class JSONNode has a quite huge common interface to make it easier to work with. The base class doesn't contain any data at all, just a common interface. So the actual data is implemented in the concrete classes. If you have vastly different classes using interfaces would make much more sense. So you keep your backing fields private / protected in the concrete classes and you're still able to access them through the interfaces in a generic way.
     
  8. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I saw this thread come up again and wanted to add two things, one of which @Bunny83 is already somewhat addressing.

    First, on perfection:
    If the number is small and they are genuinely exceptions to rules that otherwise consistently hold then I'd make sure it's communicated as clearly as possible, do what I can to reduce the likelihood of related user/designer error*, and simply accept that my code isn't going to be academically clean and pure. That won't stop it working, that won't stop me getting the job done, and it's probably not worth me wasting time over.

    I aim for textbook ideals about how code should be, but my ultimate job is to build the thing that needs to be built and maintain it afterwards, so those come first.

    * Probably using something similar to DonLoquacious's suggestion.

    Second, on the variety of tools at our disposal:
    This doesn't sound like a good candidate for the use of inheritance in the first place.

    It sounds like it might be a great candidate for component oriented design - have one component for each "function" and its related data, and only attach the components which are required for each instance. That way you have exactly the functions and associated data you need, and none of the extra stuff, and you can have whatever combinations you want without a complicated inheirtance heirarchy - possibly with none at all!

    Note that while GameObjects and Components are one implementation of a component oriented design it's not the only one. You can potentially do something more lightweight to fit your specific needs, since you probably don't need all of the baggage that a MonoBehaviour brings along with it.

    Another approach that could be worth considering is to make things "data driven". (Not to be confused with Unity's DOTS.) This is where you have code which provides generic functionality which operates in specific cases based on data it is provilded. So, instead of programming every possible variation you need you instead "configure" them. A common example of this in a game or simulation would be something like a conversation system. In most games every conversation works via the same, relatively simple set of code, with just a different configuration (ie: set of lines and relationships) passed in. A heavier example would be a Behaviour Tree. As game and sim developers there's a good chance that people here apply this to parts of their game without realising it - I bet we don't all have bespoke code for every puzzle, fight or level in our games. :)

    Of course I don't know if either of those are actually good fits for the issues people are having here, as I'm mostly only seeing very short descriptions. In any case, if you find that it's a real struggle to use a particular tool for a job and you're sure you're using the tool correctly then there's a good chance it's just not the right tool, and something else might be more appropriate.
     
    Bunny83 likes this.
  9. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    Yes, but that is not the case.

    I still need that parameter in the derived class. It is just that in this specific instance, I set the value by some logic, overwriting whatever is in the inspector. This can lead to confusion in the user who sets a value but sees it ignored/changed. That's why it would be cleaner to hide it in the inspector.
     
    Nad_B and VBenincasa like this.
  10. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    To make a specific example that might make things clear:

    I have a set of scripts that all share common features - they all have a target and a condition among other things. They also share a bunch of internal calculations and logic. All of those shared features go into the base class. Then each specific implementation goes into a derived class.

    Just for example, assume I have one class that rotates an object, one that translates it, one that changes its color, etc. - that's for example, let's not discuss if it makes sense to put those into seperate classes, in my case it does.

    One of the derived classes is different in one and only one way from all the others: Say it doesn't have a condition. It always triggers on start. All the other shared fields, shared logic, etc. are the same. Even this one class still has the condition field, it's just always set to the same value.

    To make an intermediate class for one exception strikes me as a piece of insanity. It means ALL classes need to inherit through two steps because ONE class has a tiny exception to the rule. That violates a basic design principle for me, that the exception should be the one doing things different, not everyone else.

    The "HideInInspector" tag is the closest so far to solve this issue, and it doesn't mess up the inheritance hierarchy for all classes that by themselves have absolutely no reason to not inherit from the base class.

    Moreover, I'm not even sure if changing the inheritance chain would even solve this problem, because in essence the difference is between having this one field public (and thus in the inspector) or protected (and thus not). I'm not even sure that's possible with inheritance, or it would require even more shenenigans in the intermediate class (hidden fields mirroring the actual fields or such crap).
     
  11. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    The no inheritance solution is to not bundle the action and the condition into the same class.

    Your "do stuff" MonoBehaviour could have an optional condition slot for "condition" MonoBehaviours, and check that conditon if it's there.

    Now you don't need your derived class anymore - it's just organically available by not slotting something in.

    You could make it even more modular by having "what to do", "when to do it" and "conditions" be three different behaviours, and then all of your inheritance hierarchies would be at most 2 deep, and your code would probably be easier to reason about and understand.
     
    lordofduct likes this.
  12. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,290
    I simplified my actual use case for the sake of the example. Let's just say that I need derived classes and they need multiple fields, some of which are shared among all of them, but one or two have one field that doesn't apply in that context (it is still used, just always with a fixed value).

    I was just making a simplified example to get away from speaking in the abstract.
     
    Nad_B likes this.