Search Unity

Showing (and editing) private fields in the inspector, without serialization.

Discussion in 'Scripting' started by sicklebrick, Nov 19, 2019.

  1. sicklebrick

    sicklebrick

    Joined:
    Apr 8, 2012
    Posts:
    31
    So any time you search for this, you get told

    Code (CSharp):
    1. [SerializeField]
    2. private int myVar;
    Problem: values will be serialized to disk.



    Or the other way around:
    Code (CSharp):
    1. [System.NonSerialized]
    2. public int myVar;
    Problem: now you can't see it in the editor



    Or you could try:
    Code (CSharp):
    1. #if UNITY_EDITOR
    2. public
    3. #endif
    4. int myVar;
    Problem: lol



    Or you could put the inspector into debug mode...

    Problem: you lose custom inspectors, headers, spacing, etc it's a tiny, fiddly button and the values are readonly.



    Or you could write a custom inspector:

    Problem: lol you'd spend longer writing custom inspectors than getting on with code. this isn't remotely viable. Just, no.




    So then I did try patching the editor.dll as there's a flag which determines whether a field has the serializable property, but that didn't work too well. (You'd have to go deeper into the serialization .dll and I think at that point you might as well just be making private vars public).

    Problem: didn't work, or just showed *everything*




    So the solution I've settled on for now, is to jump into the UnityEngineCore.dll and unseal the UnityEngine.SerializeField attribute.
    E.g. extend it as shown:

    Code (CSharp):
    1. #if UNITY_EDITOR
    2.    
    3.     public class  SemiSerializableAttribute : UnityEngine.SerializeField {
    4.        
    5.         public SemiSerializableAttribute(){}
    6.        
    7.     }
    8.  
    9. #else
    10.  
    11.     public class SemiSerializableAttribute : System.Attribute {
    12.        
    13.         public SemiSerializableAttribute(){}
    14.  
    15.     }
    16.  
    17. #endif
    Problem: nextlevel janky, I wouldn't know where to start.


    There must be a better way to have a private var, that the inspector can edit at runtime, since there's nothing at all in the language spec stopping it.

    E.g. not publicly exposed (giggity), but orders of magnitude quicker to debug than firing up the debugger, trapping and tweaking values.

    Any ideas?
    Cheers.
     
    koirat likes this.
  2. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    340
    You can't do this on the PropertyDrawer level because unity won't build them for non-serialized fields, so your only option (outside of modifying dlls maybe) is the hard way - changing things on a higher level.

    So you can either change this behaviour on the Editor level or go even higher level like I did with Power Inspector.

    Basically what you need is a general-purpose solution that can recreate the look of the old Editor, while changing its behaviour in this one regard.
     
  3. sicklebrick

    sicklebrick

    Joined:
    Apr 8, 2012
    Posts:
    31
    Cheers, there's some pretty sweet stuff in there, but it's not particularly usable at present.

    One or two issues:
    -few null ref errors: (PowerDrawerUtility:2481 via PowerDrawerUtility:2474)
    -invalid cast exception when dragging around some floats in a list
    -weird color scheme issues on the light theme (horz spacing bars
    -animations!
    But mostly:
    -sometimes things don't expand/contract properly even on single-click mode
    -no option to just show private fields without dropping to debug mode
    -dimmed font when selecting disabled objects

    Could you give me some pointers for modifying a few things?

    A: Where's best to modify to display private fields as if public, but just in a slightly different colour or so?

    B: Do you know roughly, off hand, where to disable the dimming effect on disabled objects?

    C: Sometimes clicking headers (in single click mode) still doesn't work. Am I going to find it possible to completely strip this click-to-select behaviour?
     
  4. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,683
    What is the problem you trying to solve with this? You may just create custom inspector what shows NonSerialized fields (or with your custom attribute inherited from it) like normal fields and that's it. Just target all MonoBehaviours with your custom inspector and you need only one. It will just reflect fields and call default drawers for them. You have fields in the inspector, you can edit their values, and they are not saved to disk, i.e. every time your assemblies/scenes/assets is reloaded you have defaults in all those fields.
     
  5. sicklebrick

    sicklebrick

    Joined:
    Apr 8, 2012
    Posts:
    31
    Oh yeah, forgot that one, sorry.
    Problem: You can't do [CustomEditor(typeof(MonoBehaviour))]

    It legit just doesn't work (and I'm not sure if it would interfere with other stuff that has custom editors, e.g. NGUI or layout tools).
     
  6. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    7,169
    I feel like there's a fundamental disconnect here right from the first sentence.

    You want to see and edit private values.... but don't want the values saved? So when you edit it, the changed value just....vanishes into the ether? Why is it important that the values not be serialized to disk? And if that is important, then what is the point of editing them if they'll just disappear?
     
  7. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,683
  8. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    340
    @sicklebrick Thanks for checking out Power Inspector!

    1. There is no class called PowerDrawerUtility in Power Inspector. Do you perhaps mean ParentDrawerUtility?
    If you could send me your log file from "C:\Users\[UserName]\AppData\Local\Unity\Editor\Editor.log" with the error contained that would be great!

    2. I am unable to reproduce this on my end. What version of Unity are you using? Seeing the exact error message would help here also.

    3. If you could send me some screenshots of in what context this is happening, it would be a big help.

    4. This is actually a feature and can be customized in the preferences

    preferences-folded-state-on-first-click.png

    The reason why I decided to change the default behavior is that in Power Inspector one might have many possible reasons for wanting to select a control (like to use copy-paste / reset / delete shortcuts), so unfolding on click could oftentimes be an undesired side-effect. I'll consider changing the default setting to work like it does in the default inspector to avoid confusion (although you are the first one who has mentioned this).

    5. That is true, you currently can't flip a switch to have non-public fields be displayed by default, but have to add the ShowInInspector attribute to each one. I'll consider implementing support for displaying all fields / properties / methods even outside of Debug Mode+ in a future update, like you can currently do with hidden components and such.

    6. This is another feature, meant to make it visually more apparent when a component is disabled. The idea is to make it clear to users that Unity's event functions won't fire for these components and that they should be treated as "being off" for all intents and purposes. I does have a negative effect on readability though, and I have been meaning to add the option to disable this in the preferences - just haven't gotten around to it quite yet.

    (Btw, we should continue discussing these in a private conversation to avoid side-tracking this public forum post further.)
     
  9. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    340
    You could modify MemberInfoExtensions.IsInspectorViewable to make non-public fields be displayed, then perhaps apply the tinting in DrawerUtility.BeginDataValidatedControl, which already handles drawing read only fields greyed out.

    Check out UnityObjectDrawer.DrawGreyedOut. You'll need to remove the "!Enabled" check inside it.
     
  10. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    340
    I agree that being able to see the contents of all private fields easily can be really helpful for debugging reasons. And the UX for enabling, using and disabled the debug mode in the default inspector isn't that great, which can lead to it not being used that often, even if it would be useful to quickly check the value of a private field.

    The biggest shortcoming of the default debug mode though, is that it can't even list all private fields, like fields with the HideInInspector attribute - not to mention fields of non-supported types like dictionaries :(

    Editing non-serialized can also be really useful sometimes, like when testing out some features in play mode.

    It's like your data has two completely different roles it needs to fulfill.

    One is being a good designer front-end that only exposes the minimum necessary amount of data, makes it 100% impossible to change anything that could potentially break things.

    The other one is being a good debugging front-end that tells you everything you want to know about its exact state, and lets you manipulate that state to your hearts content, and even lets you try to intentionally see if you can break things.
     
  11. sicklebrick

    sicklebrick

    Joined:
    Apr 8, 2012
    Posts:
    31
    Cheers, but you still can't combine that with other custom inspectors and the ordering is still a mess (especially if you want things ordered by inheritance or want your private vars under a [Header] for readability).

    Aah, the obligatory "Lol why would u even do that" post.
    Yes I want to see and edit private values, then have them puff into the ether when I'm done.
    You do realise you can edit values at runtime? Temporary variables and so forth on serializable objects.
    How come whenever someone asks a reasonable question, there's always a "why would you do that" post where the person has no interest in finding out why they might want to do that? I could just as reasonably ask "Why do you go looking for topics you don't understand and start running your mouth"... I also have no intention of finding out why. I just like acting superior.

    Sisus summed it up perfectly.

    Cheers dude!

    I'm getting quite a few errors, so I'll note them down properly in context over the next few days and drop you a PM.
    Thankfully, it's mostly just the odd null ref, so it should be easy enough to nail down.

    Don't get me wrong, I'm kinda loving it so far! Also many thanks for the help and quick responses!

    ( FWIW, "Change Folded State On First Click" is checked, but the behviour is still a little inconsistent )
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    6,874
    Yeah it does. I do it with my SPEditor:
    Code (csharp):
    1.  
    2.     [CustomEditor(typeof(MonoBehaviour), true)]
    3.     [CanEditMultipleObjects()]
    4.     public class SPEditor : Editor
    5.     {
    6.  
    https://github.com/lordofduct/space...er/SpacepuppyUnityFrameworkEditor/SPEditor.cs

    Note, you have to include the ",true" which tells the 'CustomEditorAttribute' that this editor is inherited by all MonoBehaviours.

    Anyways, you will notice in that I do a lot of overloading of the unity editor system... most of it you can ignore.

    But one that you may concern yourself with is in OnEnable and OnInspectorGUI I deal with a special attribute called 'ShowNonSerializedPropertyAttribute':
    https://github.com/lordofduct/space...uppyUnityFramework/PropertyAttributes.cs#L601

    With that I can then just attribute any non-serialized field with that and it'll show up in the inspector under a region titled "Runtime Values".

    Like so:
    Code (csharp):
    1.  
    2. [System.NonSerialized]
    3. [ShowNonSerializedProperty]
    4. private float _someValue;
    5.  
    For example:
    t_Timer.png
     
  13. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    340
    @sicklebrick

    Thanks, I'll keep an eye out for your PM!

    It's very strange that you're seeing so many errors, and that basic things like float lists aren't working for you... It could be that there's some kind of conflict with some other extension you are using. Well, we'll get to the bottom of it soon...
     
  14. sicklebrick

    sicklebrick

    Joined:
    Apr 8, 2012
    Posts:
    31
    Cheers... but I mean you can't have multiple custom inspectors running on one component.

    E.g. I Have

    Code (CSharp):
    1. [CustomEditor(typeof(Vehicle))]
    2. public class VehicleEditor{ ... }
    But since Vehicle is Monobehaviour:


    Code (CSharp):
    1. [CustomEditor(typeof(MonoBehaviour), true)]
    2. public class Blah : Editor
    3. {
    4.    
    5.     public override void OnInspectorGUI(){
    6.        
    7.         // Should appear at the top
    8.         GUILayout.Label( "Lol, this will absolutely not happen if there's a custom editor for the subclass." );
    9.        
    10.         base.OnInspectorGUI();
    11.        
    12.     }
    13.    
    14. }
    Will not work.

    I guess one solution might be to inherit all your editor classes from an intermediate wrapper that handles this, but then you've still got the other issues with generic inspectors.

    Cool idea though.


    Yeah, hopefully it's just something daft like minor 4.0 runtime changes or some jank thing I've done.
    Does seem a little odd given how well put together it all is, lol.
     
    SisusCo likes this.
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    6,874
    Yeah, your custom editors should inherit from your new editor script.

    Like in my case, I always inherit from SPEditor. As you can see here:
    https://github.com/lordofduct/space...nityFrameworkEditor/Base/SPEntityInspector.cs

    And those editors out there from 3rd parties that implement their own custom inspector... well, it'll use there's which won't work with this. But that's fine. They wrote their own inspector. You don't want their inspector to conflict with yours.
     
  16. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    825
    This problem piqued my interest, so I went and made something kind of dumb:

    Code (CSharp):
    1. // requires a sacrificial serialized variable
    2. // could be configured to still draw the field as well
    3. // so you could just stick it on the top of the first serialized variable
    4. // i was just lazy
    5. [DrawHiddenFields]
    6. [SerializeField]
    7. bool dummy;
    8.  
    9.  
    10. // annotate any non-serialized fields you want
    11. // (only limited by how many field types you want to support)
    12. // this version supports float, int, bool, and string types
    13. [ShowInInspector]
    14. float sampleFloat;
    15.  
    16. [ShowInInspector]
    17. int sampleInt;
    18.  
    19. [ShowInInspector]
    20. bool sampleBool;
    21.  
    22. [ShowInInspector]
    23. string sampleString;
    The drawer itself:

    Code (CSharp):
    1. [CustomPropertyDrawer(typeof(DrawHiddenFieldsAttribute))]
    2. public class HiddenDrawer : PropertyDrawer {
    3.  
    4.     public override void OnGUI ( Rect position, SerializedProperty property, GUIContent label ) {
    5.         var fieldsToDraw = GetDrawnFields( property );
    6.  
    7.  
    8.         var obj = property.serializedObject.targetObject;
    9.  
    10.         position.height = EditorGUIUtility.singleLineHeight;
    11.  
    12.         foreach( var f in fieldsToDraw ) {
    13.             var fieldType = f.FieldType;
    14.  
    15.             var fieldName = ObjectNames.NicifyVariableName( f.Name );
    16.             var fieldLabel = new GUIContent( fieldName );
    17.  
    18.             var fieldValue = f.GetValue( obj );
    19.  
    20.             if( fieldType == typeof( float ) ) {
    21.                 var val = (float)fieldValue;
    22.                 EditorGUI.BeginChangeCheck();
    23.                 val = EditorGUI.FloatField( position, fieldLabel, val );
    24.                 if( EditorGUI.EndChangeCheck() ) {
    25.                     f.SetValue( obj, val );
    26.                 }
    27.             } else if( fieldType == typeof( string ) ) {
    28.                 var val = (string)fieldValue;
    29.                 EditorGUI.BeginChangeCheck();
    30.                 val = EditorGUI.TextField( position, fieldLabel, val );
    31.                 if( EditorGUI.EndChangeCheck() ) {
    32.                     f.SetValue( obj, val );
    33.                 }
    34.             } else if( fieldType == typeof( int ) ) {
    35.                 var val = (int)fieldValue;
    36.                 EditorGUI.BeginChangeCheck();
    37.                 val = EditorGUI.IntField( position, fieldLabel, val );
    38.                 if( EditorGUI.EndChangeCheck() ) {
    39.                     f.SetValue( obj, val );
    40.                 }
    41.             } else if( fieldType == typeof( bool ) ) {
    42.                 var val = (bool)fieldValue;
    43.                 EditorGUI.BeginChangeCheck();
    44.                 val = EditorGUI.Toggle( position, fieldLabel, val );
    45.                 if( EditorGUI.EndChangeCheck() ) {
    46.                     f.SetValue( obj, val );
    47.                 }
    48.             }
    49.  
    50.             position.y += EditorGUIUtility.singleLineHeight + 2;
    51.         }
    52.     }
    53.  
    54.     public override float GetPropertyHeight ( SerializedProperty property, GUIContent label ) {
    55.         var fieldsToDraw = GetDrawnFields( property );
    56.  
    57.         return EditorGUIUtility.singleLineHeight * fieldsToDraw.Count + 2 * ( fieldsToDraw.Count - 1 );
    58.     }
    59.  
    60.     private static List<FieldInfo> GetDrawnFields ( SerializedProperty property ) {
    61.         var baseType = property.serializedObject.targetObject.GetType();
    62.  
    63.         var fields = baseType.GetFields( BindingFlags.Instance | BindingFlags.NonPublic );
    64.  
    65.         var fieldsToDraw = new List<FieldInfo>();
    66.  
    67.         foreach( var f in fields ) {
    68.             var attr = f.GetCustomAttributes( typeof( ShowInInspectorAttribute ), true );
    69.             if( attr.Length > 0 ) {
    70.                 fieldsToDraw.Add( f );
    71.             }
    72.         }
    73.  
    74.         return fieldsToDraw;
    75.     }
    76. }
    Note: I don't actually recommend you use this unless you're looking for something super quick and dirty. I would recommend using a custom base editor class instead. It's way less jank and way more flexible.

    This will result in an inspector like the following (none of these fields are serialized -- all will be lost on reload):

     
  17. sicklebrick

    sicklebrick

    Joined:
    Apr 8, 2012
    Posts:
    31
    Yeah, it's not a bad solution - less configurable than e.g. Power Inspector, but it's definitely very lightweight and minimally invasive! Great to have options!


    Lmao, I wouldn't have thought of this!
    I bet there's one problem out there for which this is the perfect most succinct solution.
     
  18. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    6,874
    well technically speaking all the other stuff going on in that SPEditor class of mine that I told you that you can ignore is to create what is effectively my own version of a tool similarish to Power Inspector that I made a long while back.

    But yes, Power Inspector looks like a nice plug n play tool to give you the features you want without needing to write your own code for it.
     
    SisusCo likes this.
unityunity