Search Unity

Hiding inherited public variable in the inspector?

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

  1. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Is it possible to make a public inherited variable to not show up in the inspector? Its only needed in the parent class and cluttering up the inspector for the other classes.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
  3. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    I have one problem with this, the HideInInspector attribute has to be in the class scope, but i cant access a public variable there, just able to declare them...
     
    Last edited: Dec 9, 2012
    JohnnyConnor and MrLucid72 like this.
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    What? You apply that attribute where you declare the field.

    What are you talking about?
     
  5. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    But i want it to appear in the parent class but not in the child class as i wrote in the first post. And because i declare the variable in the parent class it cant be hidden with this method in the child class it seems. Might have been a bit vague, i'm sorry about that.
     
  6. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Anyone?
     
    CSuman likes this.
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    So you have two behaviours. Let's call them CompA and CompB, CompB inherits from CompA.

    Code (csharp):
    1.  
    2. public class CompA : MonoBehaviour
    3. {
    4.     public PropA:string;
    5. }
    6.  
    7. public class CompB : CompA
    8. {
    9.     public PropB:string;
    10. }
    11.  
    Now if you add CompA to a gameobject the inspector has a property for PropA.

    If you add CompB to a gameobject the inspector has properties for both PropA and PropB.

    You want it that when you add the component for CompB there is ONLY a property for PropB, and if you add CompA there is ONLY a property for PropA?



    Why?

    I'm not trying to be mean here. I just don't understand why this is needed for you. Does CompB set PropA to something and it doesn't want it changed ever?
     
  8. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Simple.
    CompA has some config values i want to be able to set in the inspector that affects some common functions for all children, and CompB has some own specific values i want to set, this cant be that strange?
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Are these static properties or something?

    How is CompA effecting for "all children"? What "all children"? For all instances of CompB out there? So CompA is a manager for CompB?

    I don't think you understand how inheritance is supposed to work.
     
    Bunny83 likes this.
  10. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    I know how inheritance is supposed to work, don't worry about that.
    If you really need to know its a path to a folder i want to be able to set through the inspector, but what or why im doing stuff is not really a part of my original question.

    Still wondering:
    Is there a way to hide a inherited public variable so it does not show up in the inspector?
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    no, you can't

    and this doesn't make sense at all. If you attach a CompA to a gameobject and a CompB to a gameobject, and you set the property of CompA to be something... the CompB attached elsewhere isn't going to know unless it gets a reference to the OTHER CompA. This can only be done by having CompA's property be static, or if the CompB is given a direct reference to this CompA.

    Either way... CompB doesn't have to inherit from CompA for any reason.

    The point of inheritance is to inherit functionality. So CompB should have this property, so that it can have it to do its work. The CompA is arbitrary now...
     
  12. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    CompB dont need to know the property, its only used in CompA, maybe its a way to make CompB not inherit it to begin with?
    CompB inherits from CompA for many reasons, to use common functions and stuff, of course there is also other classes that inherits from CompA.
    This particular property is only for configuration of one specific thing in CompA, there are a lot more to it than that, but its not the point of this thread at all.
     
    VardenEE and mikapote like this.
  13. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Why is it only for CompA and not CompB?

    If CompA has something that CompB shouldn't have... then CompB shouldn't inherit from it.

    If you're configuring CompA, then you're configuring CompB as well! Because CompB is a CompA.

    Sounds like you're shoving way to much functionality into 1 single class. Try breaking this up into multiple classes (inheritance is not breaking up into several classes if you're just going to inherit all the functionality of each other).

    And no, you can't NOT inherit part of a class when inheriting from it. If you don't want to inherit part of it, then you should inherit at all.



    There's a reason what you want doesn't exist. Because it doesn't make sense.
     
    Last edited: Dec 12, 2012
    Bunny83 likes this.
  14. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    yeah, yeah.
    CompB uses all functions and variables of CompA, except for this single variable that is used to configure a path that a function in CompA uses, this path is same for all classes that inherits from CompA and i would like to set this from the inspector, why is that so strange? How would you implement this single variable then? And why do you care so much what and why i do something? : p

    Edit: Its not a big problem really, just a matter of cleaning up the inspector.
     
    ikoukas likes this.
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    because that doesn't make sense

    this variable, of CompA, the value you set for the single instance of it, is not going to change said value for ALL CompB's... every other CompB is still going to have the default value CompA has before setting it in the inspector. So if CompB calls this function, it will fail.

    That's fine though. If you want to use poor design, and insist that the programming language bend to your bad design instead of just fixing it, you go for it.

    Have fun.



    It's like you came in and asked, "Why won't this screw come out?"

    And you received the response, "That's a hammer... and you're banging the screw in with it."

    "Yeah, well why do you care how I use my hammer? How do I get my screw out with it!?"
     
    Last edited: Dec 12, 2012
    Bunny83 likes this.
  16. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Well, maybe some one can come and screw you instead : p
    You have no insight in my class and as such cant tell me if what im doing is right or wrong.
    Its working perfectly well what im doing and its not a bad design, all i did was asking a simple question, please keep to the subject instead of playing "mr know it all".
    Finally got my answer, thread closed.
     
  17. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    I actually gave you the answer quite some time ago.

    And I have enough insight to your class from what you explained here to know you most likely have a bad design somewhere. I can bang a screw in with a hammer... doesn't mean I should.
     
  18. Flipbookee

    Flipbookee

    Joined:
    Jun 2, 2012
    Posts:
    2,793
    Why don't you just write a custom inspector for the derived class?
     
  19. Errorsatz

    Errorsatz

    Joined:
    Aug 8, 2012
    Posts:
    555
    I'm going to have to disagree there - this is a slightly unusual case, but not necessarily bad design.

    Simple Example:
    Class: Enemy. Property: Attack animation.
    Class: Warlock, derives from enemy. Does not animate differently when attacking, instead has separately moving ghosts. Attack animation property is useless in inspector, as it is unused.


    Now technically, you could say that the hierarchy should then be:
    Enemy -> VisiblyAttackingEnemy -> ...
    Enemy -> Warlock
    Or something like that.

    But IME, there are always going to be cases where trying for a 100% pure model has more downsides than benefits.
     
  20. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    That's not a comparable scenario though.

    Their description was more like:
    Class Enemy. Property: Attack animation used by all Enemies and classes that inherit from enemy.
    Class Warlock, derives from Enemy. Enemy already has Animation defined and a function to handle it... so not necessary here.
    Attack animation property is useless in inspector because it was already set for Enemy.

    Not logically sound, or not properly defined.



    Also in your example. I wouldn't have enemy contain the AttackAnimation. And instead each type that happens to have one has the Attack Animation. Enemy would just define a method called 'Attack', and the child classes that have animations would override the function and play the animation that is needed (or do something else such in the of the Warlock). If there is a generic enemy component that Enemy is supposed to act like and can be added to gameobjects, I'd just create another class GenericEnemy and implement it there.

    class AbstractEnemy
    ->Attack

    class Warlock : AbstractEnemy
    ->override ->Attack - do something unique

    class GenericEnemy : AbstractEnemy
    *AttackAnim : Animation
    ->override->Attack - play Attack animation
     
    Last edited: Dec 12, 2012
    Bunny83 likes this.
  21. Errorsatz

    Errorsatz

    Joined:
    Aug 8, 2012
    Posts:
    555
    Nothing he's posted makes me think he's saying that. I believe he specified that the parameter was a folder path. Perhaps this child class always uses a specific folder, and therefore a publicly accessible parameter is not needed. Assuming incompetence where information is vague is not a great conversation strategy.


    The issue with that is that at the which aspects are "abstract" vs "generic" can vary from enemy to enemy. So the Warlock ignores the attack animation, but does use the other animations such as walking. Meanwhile the living statue never moves off its pedestal, and therefore ignores the walk animation (but does use the attack animation).

    The "pure" solution to this - and one that I'd use if the differences were big enough to require it - is the composition pattern, where instead of a single hierarchy you have separate components (Attack, Locomotion, etc) that can be combined in any required pattern.

    However, we're talking about a game with finite scope and finite development time. If a single enemy poses an exception to the abstraction, then bending it slightly (in a way that doesn't harm the integrity) is preferable to refactoring the entire system, especially if the end result will be clumsier to use in 99% of the cases.
     
    Last edited: Dec 12, 2012
  22. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    They said it here:

    This is a flaw in object identity.


    And yes, in a finite scope you can't be perfect. And that's why I wasn't saying your example was bad design. That was just bending the rules and there is a pure answer, but we can't all be pure.

    But I also pointed out that, as far as I know, that comparison wasn't the same.



    Anyways, this conversation has grown boring.
     
    Bunny83 likes this.
  23. KyleStaves

    KyleStaves

    Joined:
    Nov 4, 2009
    Posts:
    821
    The only way I can think to do it is to create a custom inspector for the child class. It's not hard to do, but adds an extra layer that needs to be maintained which can be annoying.

    http://docs.unity3d.com/Documentation/Components/gui-ExtendingEditor.html Should get ya started.

    EDIT: Just to make a quick comment on usage; it's entirely within the realm of quality OOP practices to have a derived class not require the same configuration values that the base class does (which is why we are able to do things like have completely different properties in the constructor of a derived class). However, it sounds like in your particular case there are some things shared between ClassA/B and some things you don't want shared. Moving forward it's absolutely worth looking into and trying to utilized a more component oriented design for situations like this (where class A/B are not related in any way other than the fact that they both require a "class C" component - which holds that shared behavior).

    Component oriented design is something that Unity is very, very strong at (and as an environment, utilizes the crap out of it themselves).
     
    Last edited: Dec 12, 2012
  24. virror

    virror

    Joined:
    Feb 3, 2012
    Posts:
    2,963
    Everything in the base class is used in the child class except for this single folder path variable thats only used in a function in the base class. Child classes dont use this value directly. I will probably just leave it as it is, making a custom inspector is not very hard, but for a singlr variable its not worth it in this case. Thanx everyone for some concrete answers for a change : )
     
    VardenEE likes this.
  25. redchurch

    redchurch

    Joined:
    Jun 24, 2012
    Posts:
    63
    I understand perfectly what OP is trying to do as I've just encountered a similar scenario. I had a bunch of objects in an array that I was trying to GetComponent on, but as I understand it that may not be the most efficient way, it might be more efficient to extend/inherit instead of reference the original class constantly through GetComponent, BUT, as virror was trying to explain, you don't necessarily want all of the parent class's variables exposed in the child object's inspector.

    In other words, it makes sense to inherit the functionality so you don't have to call GetComponent everywhere, but having all the variables of the parent class exposed in the child class inspector is problematic for entirely obvious reasons, especially when you're designing a system that other people will be using, and potentially confused by having all those variables exposed in the child's inspector panel.
     
    VardenEE, JohnnyConnor, NIOS and 3 others like this.
  26. shawnblais

    shawnblais

    Joined:
    Oct 11, 2012
    Posts:
    324
    It would be nice to get a proper fix for this, and the guys whining about use cases, and not answering the question are not helpful.

    I have a similar issue. I have a class SimpleButton, and you can select an action for that button when it is clicked.

    Then, I have a subClass, call it SpecializedButton. It needs all the logic from SimpleButton, but it uses its own custom action, so there's no sense exposing a generic action in the inspector that will never be used.

    I guess the only workaround, would be to create AbstractButton, move the bulk of logic there, then have SimpleButton implement just the Action handling, and have SpecializedButton extend AbstractButton.

    Not sure I'll bother though. Not worth it just to hide a single property;
     
  27. Vilhelmus

    Vilhelmus

    Joined:
    Dec 18, 2014
    Posts:
    20
    @lordofduct Everyone who you try to help just becomes dumber from your jibberjabber. You are obviously a moron or a drunk from your responses. You have a flaw in your brain; possibly lesions on the frontal lobe.

    Here is an actual answer, not just a whiny little baby who probably just googled his answers to begin with:
    //add to Editor folder
    Code (CSharp):
    1. public class MyClassEditor : Editor
    2. {
    3.     public MyClass my_Component = null;
    4.     public static bool my_Foldout;
    5.  
    6.     public void OnEnable()
    7.     {
    8.         my_Component = (MyClass)target;
    9.     }
    10.  
    11.     public override void OnInspectorGUI()
    12.     {
    13.             DoMyFoldout();
    14.         }
    15.  
    16.     public virtual void DoMyFoldout()
    17.     {
    18.         MyFoldout = EditorGUILayout.Foldout(MyFoldout, "My Custom Foldout");
    19.         if (MyFoldout)
    20.         {
    21.             my_Component.MyObject = (GameObject)EditorGUILayout.ObjectField("My Custom GO", my_Component.MyObject, typeof (GameObject), true);// set true to allow scene objects, else false
    22.             GUI.enabled = true;
    23.             vp_EditorGUIUtility.Separator();
    24.         }
    25.  
    26.     }
    27. }
    Then MyClass.cs
    Code (CSharp):
    1. public GameObject MyObject;
    // will only serialize in inspector for MyClass.cs using the custom inspector above so public class MyOtherClass : MyClass { will not show the variable
    This is useful for inheriting so the same variables aren't displayed in inspector multiple times in each of the inherit classes since you only need it once in the main class.
     
  28. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    This from someone who doesn't even know how to use code brackets...

    [edit]
    thank you for fixing it... you figured out the brackets. Good job.

    I repeat my previous statement. If this is something you need to accomplish, your code design is broken.

    Make an abstract version of the class minus the fields, but including the generalized implementation, then make sub classes of that with the fields that are necessary.
     
    Last edited: Dec 27, 2014
  29. Vilhelmus

    Vilhelmus

    Joined:
    Dec 18, 2014
    Posts:
    20
    You're about as useful as a dead goldfish. Go flush yourself down the toilet.
     
  30. Samo-Jordan

    Samo-Jordan

    Joined:
    Jan 15, 2014
    Posts:
    11
    Here's my solution: put a public variable called 'sentinel' (or any other name) before all other public variables in your derived class. Then put the following editor script (adjusted to your needs) somewhere in the Editor folder.

    Code (CSharp):
    1. using UnityEditor;
    2.  
    3. [CustomEditor(typeof(MyClass))]
    4. public class MyClassEditor : Editor {
    5.  
    6.     override public void OnInspectorGUI() {
    7.         SerializedObject so = new SerializedObject(target);
    8.         SerializedProperty prop = so.GetIterator();
    9.         bool enterChildren = true;
    10.         bool draw = false;
    11.         while (prop.NextVisible(enterChildren)) {
    12.             if (draw || prop.name.Equals("m_Script")) {
    13.                 EditorGUILayout.PropertyField(prop);
    14.             }
    15.             if (prop.name.Equals(“sentinel”)) {
    16.                 draw = true;
    17.             }
    18.             enterChildren = false;
    19.         }    
    20.     }
    21. }
    22.  
    This will hide all public variables above the derived class. It's easy to modify the script to hide a subset of all those variables by explicitly checking for the names of those variables. The sentinel isn't needed then, it's just convenient to be able to hide all variables upstairs without having to refer to those variables explicitly.

    In case 'm_Script' is renamed in future Unity versions, you'll lose the script field. Not a big deal IMHO.
     
    Last edited: Dec 8, 2015
    jeffersonrcgouveia and VardenEE like this.
  31. dani-unity-dev

    dani-unity-dev

    Joined:
    Feb 22, 2015
    Posts:
    174
    Just my opinion:

    1) You should extract the feature used only in the base class into a component of its own.

    2) You may want to avoid inheritance in a component based architecture (of course, it depends on a lot of things).
     
    Matti-Jokipii likes this.
  32. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Why would you necro a thread that's full of petty arguments?
     
    Bunny83 and Kiwasi like this.
  33. dani-unity-dev

    dani-unity-dev

    Joined:
    Feb 22, 2015
    Posts:
    174
    My bad.

    Edit:
    I didn't notice it was so old since the last reply was from yesterday.
     
  34. Samo-Jordan

    Samo-Jordan

    Joined:
    Jan 15, 2014
    Posts:
    11
    In case anyone is interested in the actual answer to the original question, please scroll upwards to my previous post [EDIT: the solution there doesn't work correctly, a corrected version is given further below]. I am completely astonished why people clutter a thread with so much useless stuff. After all, the original poster asked a simple question, and people getting here through search engines are interested in the same question as well, and they won't find the answer if it's in the middle, sandwiched by noise. If this cluttering goes on I will consider putting my answer into a new thread.
     
    Last edited: Dec 11, 2015
  35. dani-unity-dev

    dani-unity-dev

    Joined:
    Feb 22, 2015
    Posts:
    174
    I didn't like your solution and I wanted to explain what is better in my opinion, that's all.
     
  36. Samo-Jordan

    Samo-Jordan

    Joined:
    Jan 15, 2014
    Posts:
    11
    I just realized that my solution posted above is non-functional, because the fields can't be edited any longer. Here is the corrected version, which finally should answer the question asked by the original poster.

    Code (CSharp):
    1. using UnityEditor;
    2. [CustomEditor(typeof(MyClass))]
    3. public class MyClassEditor : Editor {
    4.     override public void OnInspectorGUI() {
    5.         SerializedObject so = new SerializedObject(target);
    6.         SerializedProperty prop = so.GetIterator();
    7.         bool enterChildren = true;
    8.         bool draw = false;
    9.         while (prop.NextVisible(enterChildren)) {
    10.             if (draw || prop.name.Equals("m_Script")) {
    11.                 EditorGUILayout.PropertyField(prop);
    12.             }
    13.             if (prop.name.Equals(“sentinel”)) {
    14.                 draw = true;
    15.             }
    16.             enterChildren = false;
    17.         }
    18.         so.ApplyModifiedProperties();
    19.     }
    20. }
    To use this script, adjust it to your needs and put it somewhere in the Editor folder, then put a public variable called 'sentinel' (or any other name) before all other public variables in your derived class. This will hide all public variables in all base classes. It's easy to modify the script to hide a subset of all those variables by explicitly checking for the names of those variables (in that case the 'sentinel' is not needed anymore).

    My advice to anyone jumping here is:
    1) Think hard whether you can/want refactor your code so that you don't need this feature, because very often there are better ways of dealing with this problem.
    2) If, after thinking hard, you are still convinced that this script serves your need, go ahead and enjoy your new feature. As I did.
     
    Last edited: Dec 11, 2015
  37. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Okay, then. I couldn't resist; general solution that doesn't require a custom editor:

    Define a property:

    Code (csharp):
    1. public class HideInSubClassAttribute : PropertyAttribute {}
    Create a drawer that checks if the property's declared in it's containing type:

    Code (csharp):
    1. using System;
    2. using System.Reflection;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. [CustomPropertyDrawer(typeof (HideInSubClassAttribute))]
    7. public class HideInSubClassAttributeDrawer : PropertyDrawer {
    8.  
    9.     private bool ShouldShow(SerializedProperty property) {
    10.         Type type = property.serializedObject.targetObject.GetType();
    11.         FieldInfo field = type.GetField(property.name);
    12.         Type declaringType = field.DeclaringType;
    13.         return type == declaringType;
    14.     }
    15.  
    16.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    17.         if(ShouldShow(property))
    18.             EditorGUI.PropertyField(position, property); //fun fact: base.OnGUI doesn't work! Check for yourself!
    19.     }
    20.  
    21.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    22.         if(ShouldShow(property))
    23.             return base.GetPropertyHeight(property, label);
    24.         else
    25.             return 0;
    26.     }
    27. }
    Usage:

    Code (csharp):
    1. public class BaseClass : MonoBehaviour {
    2.  
    3.     [HideInSubClass]
    4.     public int hideInSubClass;
    5.  
    6.     public int showInSubClass;
    7.  
    8. }
    9.  
    10. public class SubClass : BaseClass { }
    Result:
    itworkslol.png
     
    pertholdth, Zyke, wmadwand and 28 others like this.
  38. Samo-Jordan

    Samo-Jordan

    Joined:
    Jan 15, 2014
    Posts:
    11
    @Baste: Thanks a lot for your contribution, now the thread looks a lot better with multiple different approaches how to tackle the original problem. What I really like about your solution is the fact, that one can easily toggle visibility using property attributes. On the other hand there is the problem that custom property drawers can be used with serializable classes or with property attributes, but not with both at the same time. Thus if I would like to hide a field for which I already use a custom property drawer I am out of luck. If you find a way to adapt your solution to custom serializable classes please post an updated solution here.
     
  39. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Wow, so this thread is still alive.

    So here's another approach where you don't need access to the base class to remove a property from being drawn. You do need to know it's serialized name though.

    This is based under the idea of having a custom editor for ALL MonoBehaviours, it's the same approach I use for my custom SPEditor that adds a bunch of functionality:
    https://github.com/lordofduct/space.../blob/master/SpacepuppyBaseEditor/SPEditor.cs

    So first we define an attribute (doesn't have to be a PropertyAttribute, because we're extracting the info ourselves):

    Code (csharp):
    1.  
    2. public class HidePropertiesInInspectorAttribute : System.Attribute
    3. {
    4.  
    5.     private string[] _props;
    6.  
    7.     public HidePropertiesInInspectorAttribute(params string[] props)
    8.     {
    9.         _props = props;
    10.     }
    11.  
    12.     public string[] HiddenProperties
    13.     {
    14.         get { return _props; }
    15.     }
    16.  
    17. }
    18.  
    Then the editor, this editor will run for all MonoBehaviours that don't have an editor already defined for it. If an editor is defined, it will override this one.

    All it does is the basic inspector drawing that would normally be done, but if there's props to not be drawn, it'll skip them.

    Code (csharp):
    1.  
    2. [CustomEditor(typeof(MonoBehaviour), true)]
    3. [CanEditMultipleObjects()]
    4. public class CustomEditor : Editor
    5. {
    6.  
    7.     private string[] _hiddenProperties;
    8.  
    9.     protected virtual void OnEnable()
    10.     {
    11.         var tp = this.target.GetType();
    12.         var arr = tp.GetCustomAttributes(typeof(HidePropertiesInInspectorAttribute), true) as HidePropertiesInInspectorAttribute[];
    13.         if(arr != null && arr.Length > 0)
    14.         {
    15.             var set = new HashSet<string>();
    16.             foreach(var a in arr)
    17.             {
    18.                 foreach(var p in a.HiddenProperties)
    19.                 {
    20.                     set.Add(p);
    21.                 }
    22.             }
    23.             _hiddenProperties = new string[set.Count];
    24.             set.CopyTo(_hiddenProperties);
    25.         }
    26.     }
    27.  
    28.     public override void OnInspectorGUI()
    29.     {
    30.         this.DrawDefaultInspector();
    31.     }
    32.  
    33.     public new bool DrawDefaultInspector()
    34.     {
    35.         //draw properties
    36.         this.serializedObject.Update();
    37.         var result = SPEditor.DrawDefaultInspectorExcept(this.serializedObject, _hiddenProperties);
    38.         this.serializedObject.ApplyModifiedProperties();
    39.  
    40.         return result;
    41.     }
    42.  
    43.     #region Static Interface
    44.  
    45.     public static bool DrawDefaultInspectorExcept(SerializedObject serializedObject, params string[] propsNotToDraw)
    46.     {
    47.         if (serializedObject == null) throw new System.ArgumentNullException("serializedObject");
    48.  
    49.         EditorGUI.BeginChangeCheck();
    50.         var iterator = serializedObject.GetIterator();
    51.         for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
    52.         {
    53.             if (propsNotToDraw == null || !propsNotToDraw.Contains(iterator.name))
    54.             {
    55.                 EditorGUILayout.PropertyField(iterator, true);
    56.             }
    57.         }
    58.         return EditorGUI.EndChangeCheck();
    59.     }
    60.  
    61.     #endregion
    62.  
    63. }
    64.  
    Note, that when the inspector enables, it gathers up any attributes on the script's class itself that says not to draw some properties, and stores them away.

    Then during draw, it skips those that were gathered up.

    Usage would be simply:

    Code (csharp):
    1.  
    2. public class BaseClass : MonoBehaviour
    3. {
    4.  
    5.     public int valueA;
    6.     public int valueB;
    7.  
    8. }
    9.  
    10. [HidePropertiesInInspector("valueA")]
    11. public class SubClass : BaseClass
    12. {
    13.  
    14. }
    15.  
    Noting that if you ever wanted SubClass, or any other derivative there of, to continue acting this way correctly. You'll have to inherit that custom editor from this CustomEditor class... or manually skip the properties that shouldn't be drawn in that editor.




    With all this said, I still stand by my earlier comments.

    If this needs to be done... there's probably something going wrong with the architecture of the class hierarchy.
     
    Tovey-Ansell and Bunny83 like this.
  40. RandomThought

    RandomThought

    Joined:
    Jan 3, 2016
    Posts:
    3
    The point really relates to UI and the representation of the data presented to the user by the inspector. Personally I find that there are a bunch of issues concerning variable visibility and naming. As such I'm going to extend the issue and solution a little to include variable visibility/hiding as well as variable naming and labels. Although slightly off topic it's part of my more general solution.

    I agree that this topic could be symtomatic of a problem with class architecture but I don't think that it's a foregone conclusion.

    Example & Rationale

    I have a base class called 'Activatable' which is used for all things that the player can 'activate' (i.e. click on it and it does something). I have derived classes for things like doors and light switches... I have quite a few of these derived types. Activate a door and it opens, activate a light switch and the light goes on etc. In the Activatable base class I have a public member called 'Defaultly Active' which when true makes an item 'active' by default (e.g. a light is on by default). I would like to change what is displayed by the inspector in the derived classes to something that is a little more readable e.g. 'Light on by default'.

    Additionally, in some cases the behaviour of 'Defaultly Active' is dictated by a derived type e.g. a hiding place. The player is never defaultly hiding and this case can simply be handled by the 'HidingPlace' class setting 'Defaultly Active' to false when it wakes and then calling the base initialiser. This doesn't seem like a serious issue with my class hierarchy, the only problem being that the editor user (e.g. game designer) is shown a 'Defaultly Active' checkbox. Setting or clearing this checkbox for a hiding place does nothing as it's overridden by code and the checkbox just clutters the UI.

    In terms of inheritance the 'Defaultly Active' bool seems best handled by the Activatable base class rather than the behaviour being duplicated into the majority of the derived classes in order to either change what is displayed by the inspector to a more descriptive label OR to hide it completely i.e. removing functionality from the abstract 'Activatable' base and re-implementing it in most derived classes seems like the wrong direction to take when it's simply a UI issue. In this case I could quite easily do something like pass the value into the base initialiser and declare a nicely named bool in each derived class (or not declare if I don't want the checkbox) BUT am I to do this with every variable in a derived class that I'd simply like hide or have a nicer inspector label? Additionally, If I further derive from a class I've already done this to then this solution not longer works.. In many respects this defeats the purpose of inheritance. A ven diagram of intersecting variable names and variable visibility by descendant types would show where I could potentially add additional classes or interfaces to the class hierarchy in order to resolve the issue. But restructuring classes just strikes me as a bit excessive (& messy) when the issue isn't really a code issue, programmers have no issue with the variable naming. It's just a matter of pretty printing for the non-programmers i.e. I just want to change a label or hide a field.

    Basically I think that the default label naming and visibility behaviour of the inspector is not a perfect match for class inheritance when some pretty printing or masking is required. It mostly works as you'd want it to. Meta data using reflection and attributes should be able to solve the issue in a satisfactory manner.

    Cases


    Based on my current requirements I come up with these cases;
    • Hiding a field from the inspector for a derived type
    • Relabelling a field for a derived type.
    For completeness I would also add;
    • Changing the label of a field to be different from it's variable name (N.B. renaming a variable to change the inspector label will potentially lose data as the YAML will no longer refer to the field)

    Proposed Solution...


    My intial thought was that it would be handy if there was an extension to [HideInspector] that would hide a field from all descendants OR take a type name of what to hide from e.g.

    [HideInspectorDerived()]
    [HideInspectorDerived(typeof(DerivedType)]


    This behaviour is very similar to suggestions in posts above..

    For changing the label of a field of a derived type;

    [InspectorLabelDerived("New Label", typeof(DerivedType)]

    Just changing the label of a field;

    [InspectorLabel("New Label")]

    In some cases some of these are better declared as field addributes, in other cases class attributes seem nicer. Therefore I defined variants for classes;

    [HideInspectorDerived("Field Name")]
    [InspectorLabel("Field Name", "New Label")]


    These attributes seem to represent a solution to my current issues.


    Implementation

    I've quickly put together an implementation based on the solution proposed above.

    Fields can be given attributes e.g.

    Code (CSharp):
    1.  
    2. public class AttributeExample : MonoBehaviour
    3. {
    4.    [HideInspectorDerived(typeof(DerivedType))]
    5.    public string hiddenFromDerivedType    = "I am hidden from DerivedType";
    6.  
    7.    [HideInspectorDerived()]
    8.    [InspectorLabel("This class only")]
    9.    public string hiddenFromAllDecendants    = "I am hidden from ALL decendants";
    10.    
    11.    [InspectorLabel("New Label")]
    12.    public string Renamed            = "My label has been changed";
    13.    
    14.    [InspectorLabel("Derived Label", typeof(DerivedType))]
    15.    public Color RenamedInDerivedType      = Color.red;
    16.    
    17.    public bool  HiddenByDerived          = true;
    18.    public int  LabelledByDerived       = 0;
    19. }
    20.  
    A derived class can also have attributes;

    Code (CSharp):
    1.  
    2. [HideInspectorDerived("HiddenByDerived")]
    3. [InspectorLabel("LabelledByDerived", "Renamed!")]
    4. public class DerivedType : AttributeExample
    5. {
    6.  
    7. }
    8.  
    The inspector code that achieves this;

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System;
    5. using System.Collections;
    6. using System.Collections.Generic;
    7. using System.Reflection;
    8. using System.Linq;
    9.  
    10. /// <summary>
    11. /// Custom attributes
    12. /// </summary>
    13.  
    14. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = true)]
    15. public class HideInspectorDerived : Attribute
    16. {
    17.    public readonly Type    m_derivedtype;
    18.    public readonly string  m_fieldname;
    19.  
    20.    public HideInspectorDerived(Type derivedtype) // Hide from specific derived type
    21.    {
    22.      m_derivedtype    = derivedtype;
    23.      m_fieldname    = "";
    24.    }
    25.  
    26.    public HideInspectorDerived()           // Hide from all descendants
    27.    {
    28.      m_derivedtype    = null;
    29.      m_fieldname    = "";
    30.    }
    31.  
    32.    public HideInspectorDerived(string fieldName) // Hide field from class
    33.    {
    34.      m_derivedtype    = null;
    35.      m_fieldname    = fieldName;
    36.    }
    37. }
    38.  
    39. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = true)]
    40. public class InspectorLabel : Attribute
    41. {
    42.    public readonly string  m_label;
    43.    public readonly Type    m_derivedtype;
    44.    public readonly string  m_fieldname;
    45.  
    46.    public InspectorLabel(string label)            // relabel field
    47.    {
    48.      m_label        = label;
    49.      m_derivedtype    = null;
    50.      m_fieldname     = "";
    51.    }
    52.  
    53.    public InspectorLabel(string label, Type derivedtype)    // relabel field for specific  derived type
    54.    {
    55.      m_label      = label;
    56.      m_derivedtype    = derivedtype;
    57.      m_fieldname     = "";
    58.    }
    59.  
    60.    public InspectorLabel(string fieldName, string label)  // relabel field for class
    61.    {
    62.      m_label        = label;
    63.      m_derivedtype    = null;
    64.      m_fieldname  = fieldName;
    65.    }
    66. }
    67.  
    68.  
    69. #if UNITY_EDITOR
    70.  
    71. [UnityEditor.CustomEditor(typeof(MonoBehaviour), true)]
    72. [CanEditMultipleObjects()]
    73. public class CustomInspectorAttribEditor : UnityEditor.Editor
    74. {
    75.    struct CustomFieldInfo
    76.    {
    77.      public bool     m_displayField;
    78.      public string    m_label;
    79.    };
    80.  
    81.    Dictionary<string, CustomFieldInfo> m_customField = new Dictionary<string, CustomFieldInfo>();
    82.  
    83.    protected virtual void OnEnable()
    84.    {
    85.      Type        monoType        = this.target.GetType();
    86.      FieldInfo[]    componentFields   = monoType.GetFields(BindingFlags.Instance | BindingFlags.Public);
    87.      FieldInfo[]    declaredtFields   = monoType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
    88.      CustomFieldInfo custominfo;
    89.  
    90.      foreach(FieldInfo field in componentFields)
    91.      {
    92.        bool customField = false;
    93.  
    94.        custominfo.m_displayField    = false;
    95.        custominfo.m_label          = "";
    96.  
    97.        InspectorLabel labelattrib  = Attribute.GetCustomAttribute(field, typeof(InspectorLabel)) as InspectorLabel;
    98.        
    99.        if ((labelattrib != null) && ((labelattrib.m_derivedtype == null) || labelattrib.m_derivedtype.IsAssignableFrom(monoType)))
    100.        {
    101.          custominfo.m_label       = labelattrib.m_label;
    102.          custominfo.m_displayField = true;
    103.          customField         = true;
    104.        }
    105.              
    106.        HideInspectorDerived hidederived = Attribute.GetCustomAttribute(field, typeof(HideInspectorDerived)) as HideInspectorDerived;
    107.        
    108.        if(hidederived != null)
    109.        {
    110.  
    111.          if((hidederived.m_derivedtype != null) && hidederived.m_derivedtype.IsAssignableFrom(monoType))
    112.          {
    113.            custominfo.m_displayField = false;
    114.            customField         = true;
    115.          }
    116.          else if((hidederived.m_derivedtype == null)  && (!declaredtFields.Contains(field)))
    117.          {
    118.            custominfo.m_displayField = false;
    119.            customField         = true;
    120.          }
    121.        }
    122.  
    123.        if(customField && !m_customField.ContainsKey(field.Name))
    124.          m_customField.Add (field.Name, custominfo);
    125.      }
    126.  
    127.      HideInspectorDerived [] hidelist = monoType.GetCustomAttributes(typeof(HideInspectorDerived), false) as HideInspectorDerived[];
    128.      if((hidelist != null) && (hidelist.Length > 0))
    129.      {
    130.        custominfo.m_displayField    = false;
    131.        custominfo.m_label          = "";
    132.  
    133.        foreach(HideInspectorDerived hide in hidelist)
    134.        {
    135.          if(hide.m_fieldname != "")
    136.          {
    137.    
    138.            if(m_customField.ContainsKey(hide.m_fieldname))
    139.            {
    140.              m_customField[hide.m_fieldname] = custominfo; // overwrite existing info...
    141.            }
    142.            else
    143.            {
    144.              m_customField.Add (hide.m_fieldname, custominfo);
    145.            }
    146.          }
    147.        }
    148.      }
    149.  
    150.      InspectorLabel [] labellist = monoType.GetCustomAttributes(typeof(InspectorLabel), false) as InspectorLabel[];
    151.      if((labellist != null) && (labellist.Length > 0))
    152.      {
    153.        custominfo.m_displayField    = true;
    154.        custominfo.m_label          = "";
    155.        
    156.        foreach(InspectorLabel label in labellist)
    157.        {
    158.          if((label.m_fieldname != "") && (label.m_label != ""))
    159.          {
    160.            custominfo.m_label = label.m_label;
    161.            if(m_customField.ContainsKey(label.m_fieldname))
    162.            {
    163.              m_customField[label.m_fieldname] = custominfo; // overwrite existing info...
    164.            }
    165.            else
    166.            {
    167.              m_customField.Add (label.m_fieldname, custominfo);
    168.            }
    169.          }
    170.        }
    171.      }
    172.    }
    173.    ////////////////////////////////
    174.  
    175.    public override void OnInspectorGUI()
    176.    {
    177.      DrawDefaultInspector();
    178.    }
    179.    ////////////////////////////////
    180.  
    181.    public new bool DrawDefaultInspector()
    182.    {
    183.      this.serializedObject.Update();
    184.  
    185.      if (this.serializedObject == null)
    186.        throw new System.ArgumentNullException("serializedObject");
    187.  
    188.      MonoBehaviour    mono     = serializedObject.targetObject as MonoBehaviour;
    189.      Type        monoType = mono.GetType();
    190.  
    191.      EditorGUI.BeginChangeCheck();
    192.  
    193.      SerializedProperty  iter     = serializedObject.GetIterator();
    194.      GUIContent        label     = new GUIContent();
    195.      CustomFieldInfo    info;
    196.      bool          enterChildren = true;
    197.  
    198.      while(iter.NextVisible(enterChildren))
    199.      {
    200.        if(m_customField != null && m_customField.TryGetValue(iter.name, out info))
    201.        {
    202.          if(info.m_displayField)
    203.          {
    204.            if(info.m_label != "")
    205.            {
    206.              label.text = info.m_label;
    207.              EditorGUILayout.PropertyField(iter, label, true);
    208.            }
    209.            else
    210.            {
    211.              EditorGUILayout.PropertyField(iter, true);
    212.            }
    213.          }
    214.        }
    215.        else
    216.        {
    217.          EditorGUILayout.PropertyField(iter, true);
    218.        }
    219.  
    220.        enterChildren = false;
    221.      }
    222.  
    223.      bool result = EditorGUI.EndChangeCheck();
    224.      this.serializedObject.ApplyModifiedProperties();
    225.  
    226.      return result;
    227.    }
    228. }
    229. #endif
    230.  
    Notes

    It's not been extensively used or tested so it's E&OE

    Field attributes work for all descended types e.g. [InspectorLabel("Derived Label", typeof(DerivedType))] will change the label of the field displayed for DerivedType and DerivedType's descendants.

    Class attributes don't work for all descendants (at least not all the time)

    Class attributes override field attributes where they refer to the same field. This can potentially be useful. It is assumed that if there is an InspectorLabel defined as a class attribute then this overrides any previous HideInspectorDerived attribute from an inherited class. This means you can re-expose fields in decedents.

    e.g.

    Code (CSharp):
    1.  
    2. [InspectorLabel("hiddenFromAllDecendants", "Re-exposed in decendent!")]
    3. public class Decended : DerivedType
    4. {
    5. }
    6.  
    e.g given classes X, Y and Z (where Z derives from Y, and Y derives from X) X could declare a field hidden from all decendents, Y would then not display it BUT Z could re-expose it etc.

    Performance of the editor could potentially be an issue as this code runs for all MonoBehaviour that don't have their own inspector UI. If it becomes an issue, I'd suggest changing;

    [UnityEditor.CustomEditor(typeof(MonoBehaviour), true)]

    To a more sepecific class type e.g. in my example case it could be;

    [UnityEditor.CustomEditor(typeof(Activatable), true)]

    ... This only works for things that don't define their own inspectorUI.

    It will moan if you try to add more than one HideInspectorDerived or InspectorLabel to a field as a field attributes. You can add lots of them to a class & it shouldn't moan.

    A property draw based solution might work better?
     
  41. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    I think you might be looking at it upside down.

    In this case the parent class must know all child classes that will derive from it. This isn't useful if the person who wrote the parent class is independent of the person who inherited from it.

    Note my solution above, where it allows the child class to elect to hide a parent classes property.

    Though again... I still believe if this problem occurs, you're approaching inheritance incorrectly.
     
    Bunny83 likes this.
  42. RandomThought

    RandomThought

    Joined:
    Jan 3, 2016
    Posts:
    3
    It works both ways round, either as a field attribute OR a class atrribute. When used a field attribute it's up to the parent, when used as a class attribute it's up to the descendant. It isn't true that a parent class must know all derived types as it can be used to hide a field from ALL descendents by simply omitting the type from [HideInspectorDerived()] (similarly to Baste's solution). I considered that a parent class might elect to hide a field and allowed it to do so. The alternative was that every derived class must hide it with a class attribute i.e. having only a class attribute potentially requires implementing in many places rather than one. However, as you point out, there can certainly also be cases where it's advantageous for the child classes to do it. So I supported that as well.

    When used as a field attribute it's also inherited properly, hence the support for [HideInspectorDerived(typeof(derivedType))]. Additionally, if the person who wrote the parent class is different from the person who writes a derived class, either of them can elect to hide a field without changing the other persons code.

    As I mentioned, I believe it can be indicative, just not foregone. I think you could almost as easily argue that [HideInspector] should not be required because you shouldn't have public fields that you don't want to display... A pure class solution can certainly be excessive when there's a large class structure that needs to support changes to it's operation at the whim of game designers. Cluttered UI is also bad.
     
  43. qqqbbb

    qqqbbb

    Joined:
    Jan 13, 2016
    Posts:
    113
    I like this solution but it does not work for arrays. Any way to fix it?
     
  44. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    I think it's because the property drawers do no longer apply to the array itself, but apply to all the elements instead.

    I haven't tried any of the solutions in this thread, as I personally agree with @lordofduct's point of view and I'd rather solve this in a different way.

    Anyway:
    Using @Baste's solution, you could try wrapping the array into a serializable struct. It's ugly to be honest, but it is probably the fastest way to work around that.

    Or try the other solution provided by @lordofduct, which actually (imo) offers more flexibility when it comes to several subtypes which have different requirements, i.e. some still need the property to be drawn while others don't...
     
    qqqbbb likes this.
  45. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    I've since done some testing.

    If you want an attribute to apply just to the array instead of to the array's elements (like the built-in Header and Space), you have to implement the drawer by inheriting from DecoraterDrawer instead of CustomPropertyDrawer. The problem with that is that DecoraterDrawer doesn't draw your property, it just draws stuff around it.

    So afaik, you can't have custom drawers for arrays by default.
     
    qqqbbb and Suddoha like this.
  46. PixelLifetime

    PixelLifetime

    Joined:
    Mar 30, 2017
    Posts:
    90
  47. KiddUniverse

    KiddUniverse

    Joined:
    Oct 13, 2016
    Posts:
    115
    Sorry to bump this old ass thread, but...

    I'm creating a player controller, but it was getting a little large, so I thought it'd be smart to break it down. I created an attack controller, and inherited from my player controller, but now all my player controller variables are in my inspector twice.

    So i figured i'd look for a way to hide them, but now there's all this debate in here about whether using this is a good idea or not. Can someone explain why this is a bad use of inheritance?

    The reason I inherited from the player controller was because of how many variables the controllers share when dealing with locomotion. I could always make a reference to my original player controller and make the variables public, but by inheriting from it, don't I allow myself a degree of protection?
     
    MCLiving88 likes this.
  48. Because inheritance is not for this case.

    If you have two scripts: Script1 inherits from MonoBehaviour and Script2 inherits from Script1 and you add some extra stuff, then your intent is just use Script2 with all the bells and whistles.
    If you want to use a certain set of variables you have three great way to do that:
    - you create a scriptable object (configsomething) and address it from both scripts (although it needs to be created separately, see HDRP asset file or Post Processing config asset or animation assets, etc)
    - you create a struct or class (not inherit from MonoBehaviour), make it [System.Serializeable], address it from both scripts

    But the best way is if you make a "main component" like PlayerController or something. And the other Script2 just has the [RequireComponent(typeof(PlayerController))] in the front of the class definition and in awake you store a reference to it (GetComponent<PlayerController>) and keep the variables public.
     
    J1wan and KiddUniverse like this.
  49. Zelioz

    Zelioz

    Joined:
    Jul 4, 2018
    Posts:
    1

    Hm so what about this, create the class that holds all the variables and declare them as protected, and then make another class that sets the data using public variables that will show up in the inspecter? If you understand what i mean
     
  50. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    Public/serialized variables showing up in the inspector would be fields. Properties (with getters and setters) are not serialized, and won't show up. Creating fields that will show up in the inspector will not set the values on the protected fields in any other class, inherited or otherwise. A property drawer or custom editor would be required to pull this off in any conceivable scenario.

    That said, you can use a custom editor and call DrawPropertiesExcluding() to simplify this task immensely- it's an undocumented function usable in OnInspectorGUI that would work to just not draw certain serialized fields (and would work fine for those inherited from a base class). That's not really a great idea though- it defeats the purpose of serializing those fields, and there are cleaner ways to accomplish most goals. I absolutely agree with the stance that hiding members only in derived types is a mistake in methodology, but it's an imperfect world.

    Edit: Here's a clean way to use attributes to accomplish hiding properties on any arbitrary MonoBehaviour-derived type that doesn't have a custom editor (untested).

    The attribute 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. }
    The editor class to exclude properties defined in the attribute:
    Code (csharp):
    1. [CustomEditor(typeof(MonoBehaviour))]
    2. public class MonoBehaviourEditorWithExcludedProperties : Editor
    3. {
    4.     private string[] propertiesToHide;
    5.     private bool hasHiddenProperties = false;
    6.  
    7.     private void OnEnable()
    8.     {
    9.         var attrs = (HidePropertiesAttribute[])target.GetType().GetCustomAttributes(typeof(HidePropertiesAttribute), false);
    10.         if (attrs != null && attrs[0].PropertyNames != null)
    11.         {
    12.             propertiesToHide = attrs[0].PropertyNames;
    13.             hasHiddenProperties = true;
    14.         }
    15.     }
    16.  
    17.     public override void OnInspectorGUI()
    18.     {
    19.         if (hasHiddenProperties)
    20.         {
    21.             serializedObject.Update();
    22.             EditorGUI.BeginChangeCheck();
    23.             DrawPropertiesExcluding(serializedObject, propertiesToHide);
    24.             if (EditorGUI.EndChangeCheck())
    25.                 serializedObject.ApplyModifiedProperties();
    26.         }
    27.         else
    28.             base.OnInspectorGUI();
    29.     }
    30. }
    And finally, how it's used, note that m_Script hides the little script property at the top of each component in the inspector, included automatically in all MonoBehaviours:
    Code (csharp):
    1. [HideProperties("m_Script", "SomePropertyName", "SomeOtherPropertyName")]
    2. public class SomeClass : MonoBehaviour
    3. {
    4.  
    5. }
     
    Last edited: Jun 27, 2019
    bigvalthoss and Omti1990 like this.