Search Unity

Editor only variables

Discussion in 'Immediate Mode GUI (IMGUI)' started by CDF, Aug 8, 2014.

  1. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    Hello, I'm sure this question has come up a lot, as I can tell from my endless google searches.

    I'm looking for a way to save variables for an object for use only within an Editor script.
    Take this example:

    Code (csharp):
    1.  
    2. class MyClass : MonoBehavior {
    3.  
    4.     public string myString = "hello";
    5.    
    6.     //do not want this variable exposed to other runtime scripts
    7.     public bool isExpaned;
    8. }
    9.  
    10. class MyClassEditor : Editor {
    11.  
    12.     public override void OnInspectorGUI() {
    13.    
    14.         ((MyClass)target).isExpanded = GUILayout.Toggle(((MyClass)target).isExpanded);
    15.     }
    16. }
    17.  
    How can I limit the access to "isExpanded" so that only the MyClassEditor can read/write it?

    some thoughts:
    1. Make it private and Serialized, use Reflection to change?
    2. Make a ScriptableObject which contains the "isExpanded" info, but, how can I link the MyClass object to the Scriptable object? In other words, how can I load a scriptable object based on another object: "MyClass"?
     
  2. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    I think scriptable object is the way to go. However, seems like it needs to be a .asset file. My question is

    How to create this .asset file when the object is not a prefab?
    How to delete this .asset file when the object has been deleted?
     
  3. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    #if UNITY_EDITOR
    ?
     
  4. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    that would still enable other classes (at edit time) to still access the "isExpanded" variable.

    there's nothing technically wrong with that, however it doesn't make much sense having a variable in a class which has no purpose during runtime. IsExpanded is purely used by the editor for the "MyClass" object, nothing else needs to know/care about it.
     
  5. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
    If it's private and serialized, you can just used serializedProperty, no need for reflection.
     
  6. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    Thanks for the help. I've settled on Reflection.

    SerializedObject would require quite a major rewrite of my Editor, I'm dealing with heavily nested data within arrays.

    I looked into saving a .asset file with a scritpable object inside, but the flow was a little ugly for the end user. Especially when you're dealing with Prefabs, Prefab instances and scene objects. I did attempt to create a master .asset file, but trying to keep that in-sync with all objects was becoming messy.

    Reflection works and is simple.
     
  7. DarkArts-Studios

    DarkArts-Studios

    Joined:
    May 2, 2013
    Posts:
    389
    If you truly want each individual object to know whether it is "isExpanded" or not then simply use it within the GameObject as you have and use:

    Code (CSharp):
    1. #if UNITY_EDITOR
    2.     publicbool isExpanded;
    3. #endif
    ... as suggested by hpjohn, remember this won't be compiled into your game so it'll _only_ exist within the editor, which is what you want? right? In my opinion there's really no need to overcomplicate this.

    However, if you don't mind it being expanded / unexpanded in the same state for all inspector views for that same type then simply rather use something like:

    Code (CSharp):
    1. EditorPrefs.SetBool(
    2.     "MyProject.MyClass.isExpanded",
    3.     GUILayout.Toggle(
    4.         EditorPrefs.GetBool( "MyProject.MyClass.isExpanded", false )
    5.     )
    6. );
    This just means that all GameObjects of that type will be expanded/unexpanded at the same time, which in some cases does not matter.

    See EditorPrefs
     
  8. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    the "isExpanded" is just an example. When I say Editor only variables, I mean variables which can only be accessed by the CustomEditor for the specific object. Example:

    Code (csharp):
    1.  
    2. class MyClass : MonoBehavior {
    3.  
    4.     public string myString = "hello";
    5.  
    6.     //do not want this variable exposed to other runtime scripts
    7.     public bool isExpaned;
    8. }
    9.  
    10. class MyClassEditor : Editor {
    11.  
    12.     public override void OnInspectorGUI() {
    13.  
    14.         ((MyClass)target).isExpanded = GUILayout.Toggle(((MyClass)target).isExpanded);
    15.     }
    16. }
    17.  
    18. class MyOtherClass : MonoBehavior {
    19.  
    20.     void Awake() {
    21.    
    22.         MyClass mc = GetComponent<MyClass>();
    23.    
    24.         //don't want to allow this below, no other class should be able to access the "isExpanded" property,
    25.         //or even know of its existence
    26.    
    27.         mc.isExpanded = false;
    28.     }
    29. }
    30.  
    Adding "UNITY_EDITOR" around the "isExpanded" property still allows "MyOtherClass" to access it when running inside Unity.

    EditorPrefs are not an ideal solution. Imagine exporting your package to a .unitypackage file and then import into a new project. You editor prefs are lost.

    Reflection is super simple:

    Code (csharp):
    1.  
    2. class MyClassEditor : Editor {
    3.  
    4.     public override void OnInspectorGUI() {
    5.  
    6.         FieldInfo field = target.GetType().GetField("isExpanded", BindingFlags.NonPublic | BindingFlags.Instance);
    7.  
    8.         field.SetValue(target, GUILayout.Toggle((bool)field.GetValue(target), "isExpanded"));
    9.     }
    10. }
    11.  
    With some more advanced coding you could probably come up with a way to cache these fieldInfo's if there's any performance concern
     
    Last edited: Aug 12, 2014
    sejton likes this.
  9. DarkArts-Studios

    DarkArts-Studios

    Joined:
    May 2, 2013
    Posts:
    389
    Perhaps I'm completely just not getting why you're needing this in the first place.

    What's to stop MyOtherClass from using the same reflection pattern with SetValue?
     
  10. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    nothing, and that's part of the problem. I would prefer not having the "isExpanded" variable in the "MyClass" at all.

    Reflection was a simple way to hide these properties (for the most part) while still enabling the custom editor access.

    If there's a standardized way of handling this situation I'd love to hear it. For example, with 2d toolkit, .asset files are created for SpriteCollections and SpriteAnimations which contain editor only data relating to those objects. These .asset files seem to be created in a "Resources/tk2d" directory, and referenced by the objects GUID for lookup. However, since in my API, Components can be created on Scene, Prefab and Prefab Instance objects, sometimes an object doesn't have a GUID, so creating a .asset file and linking it to its related object is a little messy. For example, creating an object in the scene, adding the component, creating the asset file, then creating a prefab out of that object. As a result, the object receives a GUID and the instanceID changes, resulting in another .asset file, which seems very messy to me.

    so without using EditorPrefs, SerializedObjects (which still enables Reflection) how can I save editor only data for an object? given that the object can be a SceneObject, Prefab, Prefab instance and be interchanged between those types.

    *The reason I'm doing this - I'm building an API/Editor and don't want variables exposed in the API that don't make sense. Say if "MyClass" was "Player" and "isExpanded" was "scrollBarPosition". That variable doesn't make sense in the context of Game logic. However, it does in the Editor logic.
     
  11. DarkArts-Studios

    DarkArts-Studios

    Joined:
    May 2, 2013
    Posts:
    389
    I think you've covered all the problems & there is, as far as I'm aware having also looked into this, no clean way to separate editor data -- because it's so tied in with the actual object itself. The "cleanest" way (in my opinion, and for your own sanity) to do this still exposes the editor settings within the gameobject however it's obviously removed from actual game (runtime) api and isn't included in an actual game compile and that is doing something like this (which I've done with some of my editior extensions that required it):

    Code (CSharp):
    1. [System.Serializable]
    2. class MyClassEditorSettings
    3. {
    4.     public bool isExpanded;
    5.     // ... etc ...
    6. }
    7.  
    8. class MyClass : MonoBehaviour
    9. {
    10.     public bool myRuntimeSetting;
    11.     public int myOtherRuntimeValue;
    12.  
    13.     #if UNITY_EDITOR
    14.         public MyClassEditorSettings editorSettings;
    15.     #endif
    16.     // ... etc ...
    17. }
    At least then it's obscured from the end-user somewhat and folded into an obvious area. That said, this also allows them access (through the API) to actual editor settings if they need it, for example by extending your editor extension with their own small script to, say, set "isExpanded" to true if they add some other component.
     
    sejton likes this.
  12. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    Thanks for the help, that does look like a good solution.

    make it private and use reflection... ok I'll stop now ;)
     
    sejton likes this.
  13. DarkArts-Studios

    DarkArts-Studios

    Joined:
    May 2, 2013
    Posts:
    389
    Glad I could (kinda) help. Hope it works for you, at least if you choose to use private/reflection within a separate class it'll mean you only have to do it to get to the settings object at which point you can just use all your settings normally.