Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Custom Editor - How to preserve state of GUI Elements like foldouts?

Discussion in 'Scripting' started by Stephan-B, Sep 27, 2013.

  1. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    What is the best way to handle saving the state of GUI elements as they are deselected and re-selected?

    There has to be a way besides EditorPrefs?

    This is for a Custom Material Editor so these bool that I am trying to preserve only live within the Custom Material Editor, I can't easily store that into the Shader...
     
    Last edited: Sep 27, 2013
  2. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    I modified the title to clarify that this is for a Custom Material Editor.

    Any Unity gods care to provide some insight here?

    With a Custom Inspector Editor adding a serialized property in the inspected class isn't an issue but with a custom material editor, adding an extra property inside the shader to track GUI panels (expanded or not) doesn't make much sense. So for custom material editor, how should we track / store the state of GUI elements like a panel / foldout which isn't a property of the shader?
     
    Last edited: Sep 27, 2013
  3. chronos78

    chronos78

    Joined:
    Dec 6, 2009
    Posts:
    154
    Two methods come to mind.

    First if you want quick and dirty with the custom inspector state being the same for all materials you could use EditorPrefs.

    But if you want a solution that has unique states for each material it gets more complicate. Such as creating a ScriptableObject that serializes the fields you need and then use the AssetDatabase.AddObjectToAsset() to attach the ScriptableObject to the material in question. Then use the custom inspector to access the fields you saved in the ScriptableObject.
     
  4. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    Often I just use static variables to preserve the state of custom foldouts within editor interfaces. Yes this does mean that the state is forgotten when Unity restarts, but I have not found this to be an issue.
    Code (csharp):
    1.  
    2. public class Demo : Editor {
    3.     private static bool _someFoldout;
    4.  
    5.     public override void OnInspectorGUI() {
    6.         _someFoldout = EditorGUILayout.Foldout(_someFoldout, "Some Foldout");
    7.         if (_someFoldout) {
    8.         }
    9.     }
    10. }
    11.  
     
    etsyletarte likes this.
  5. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    I did one implementation with EditorPrefs using the Material name but then you end up with lots of keys. It works but as you said it is sort of a dirty implementation.

    Your second suggestion would work but as you said that is more complicated. My materials are already a sub-asset of some other ScriptableObject. Hopefully, adding sub-assets to other sub-assets works. With my luck, I'll run into some unreported bug lol

    I also thought of adding an extra property in the Shader and set it to 1 or 0 for each material based on the state. But again, not an ideal solution.

    I could also create some custom Material editor manager to track instance ID of material and their state(s).

    But all of this seems like convoluted ways to simply track the state of an expandable / foldout ui element.

    I am hoping that with the addition of Shader Combinations we'll get something in there. However, I cannot find any information on this besides what was said at Unite in Vancouver.
     
  6. ChaseRLewis73003

    ChaseRLewis73003

    Joined:
    Apr 23, 2012
    Posts:
    85
    If you want it perObject instead of just 'per type' like numberkruncher (I'd advise his way).

    You can use preprocessor definitions to specify properties just for the UnityEditor

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. public class Foo : MonoBehaviour
    5. {
    6.  
    7. #if UNITY_EDITOR
    8. //Put gui elements for custom inspector here
    9. public bool exampleFoldout = true;
    10. #endif
    11. }
    12.  
    Code (csharp):
    1.  
    2. using UnityEditor;
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6. [CustomEditor(typeof(Foo))]
    7. public class FooEditor : Editor
    8. {
    9.    public override void OnInspectorGUI()
    10.   {
    11.      Foo foo = target as Foo;
    12.      
    13.      foo.exampleFoldout = EditorGUILayout.Toggle("SomeName",foo.exampleFoldout);
    14.      if(foo.exampleFoldout)
    15.      {
    16.          //Show stuff......
    17.      }
    18.    }
    19. }
    20.  
    Haven't checked this code in particular but I've done some inspectors like this though most are as others have shown above.
     
  7. Stephan-B

    Stephan-B

    Joined:
    Feb 23, 2011
    Posts:
    2,269
    For Custom Editors it's pretty easy to add properties to track those states in the target class (as you guys suggested). However, for Custom Material Editors, the equivalent would be adding those properties inside the Shader which I would rather not do.

    Right now I am tracking the state of those foldouts for each Material in a List<>. Although this works fine, I would still like to hear from Unity and get their insight / suggestions on how to handle this.
     
  8. TRIAX GAME STUDIOS

    TRIAX GAME STUDIOS

    Joined:
    Nov 27, 2013
    Posts:
    49
    STEPHAN ! Were you Able to Do the foldouts in material Properties In the end ?

    Iv Been looking for a long time on how to Implement Fold outs in Sections of the material Inspector like This Below
    So we can fold out parts of the materials / even have Foldouts inside foldouts ...



    Here is how SHould look in the Material Properties :


    http://imagizer.imageshack.us/v2/800x600q90/18/pses.jpg

    There is this Infos At unity Documentation :

    Foldout properties : http://docs.unity3d.com/Documentation/ScriptReference/EditorGUI.Foldout.html
    Example on CUstom Material Editor : http://docs.unity3d.com/Documentation/Components/SL-CustomMaterialEditors.html

    But im a nob and reaaly cant make it : )

    Could you please show a Complete / material - Script Example on how to Implement it ?

    THANKS !!
     
  9. MaxFr77

    MaxFr77

    Joined:
    Nov 9, 2015
    Posts:
    7
    I bumped into the same issue building my own UberShader and I wish someone answered this question !!

    From my research, it seems the problem is that the MaterialEditor does not have its own SerializeObject that you can modify. Its SerializeObject is in fact the Material itself and I have not found a way to change that. So it is very different than the usual custom inspector where you can change the inspected class to serialize stuff.

    Looking at various refs and at Unity's standard shader (StandardShaderGUI.cs in the standard shaders), the workaround is storing the fields you want to serialize as Material properties (which also have to be added to the shader). It could also be saved as keywords if you just want to save bools or simple enums.

    here's an example with material properties for BlendMode in the MaterialEditor derived Class:
    Code (CSharp):
    1. // retrieving blendmode value from material:
    2. MaterialProperty blendMode = null;
    3. blendMode = FindProperty ("_Mode", props);
    4. float mode = (BlendMode)blendMode.floatValue;
    5.  
    6. // setting new blendMode value in material:
    7. BlendMode newBlendMode = BlendMode.Opaque;
    8. material.SetFloat("_Mode", (float)newBlendMode );
    in the shader:

    Code (CSharp):
    1. Shader "Standard"
    2. {
    3.     Properties
    4.     {
    5.         // _Mode is just used for storage, never used in the shader
    6.         [HideInInspector] _Mode ("__mode", Float) = 0.0  
    7.     }
    8. // other stuff...
    Frankly I'm not too happy about this!
    Stephan if you found something better, I'd love to hear!
     
  10. GearKlik

    GearKlik

    Joined:
    Sep 21, 2015
    Posts:
    58
    TLDR: It's fine to use shaderLab properties to store GUI properties

    I ran into exactly the same problem but I think everything's cool. I've done some testing and using shaderLab properties is safe for storing GUI only properties. If the shaderLab property is not used in the final output of the vertex or fragment shaders then it isn't compiled into the final shader.

    To test this I createsd a simple unlit shader:
    Code (CSharp):
    1. Shader "Unlit/CompileTester"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.         _UnusedTex("UnusedTex", 2D) = "white" {}      
    7.         _UnusedFloatA("UnusedFloatA", Float) = 1
    8.         _UnusedFloatB("UnusedFloatB", Float) = 1
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Opaque" }
    13.         LOD 100
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag          
    20.  
    21.             struct appdata
    22.             {
    23.                 float4 vertex : POSITION;
    24.                 float2 uv : TEXCOORD0;
    25.             };
    26.  
    27.             struct v2f
    28.             {
    29.                 float2 uv : TEXCOORD0;              
    30.                 float4 vertex : SV_POSITION;
    31.             };
    32.  
    33.             sampler2D _MainTex;
    34.             //sampler2D _UnusedTex;
    35.             float _UnusedFloatA;
    36.             float _UnusedFloatB;
    37.            
    38.             v2f vert (appdata v)
    39.             {
    40.                 v2f o;
    41.                 o.vertex = UnityObjectToClipPos(v.vertex);
    42.                 o.uv = v.uv;              
    43.                 return o;
    44.             }
    45.            
    46.             fixed4 frag (v2f i) : SV_Target
    47.             {
    48.                 // sample the texture
    49.                 //float usedAB = _UnusedFloatA * _UnusedFloatB;
    50.                 fixed4 col = tex2D(_MainTex, i.uv);I
    51.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA);              
    52.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA * _UnusedFloatB);
    53.                 return col;
    54.             }
    55.             ENDCG
    56.         }
    57.     }
    58. }
    59.  
    I then compiled 4 different versions 1 test and used WinMerge to compare the compiled shaders:

    #1. Don't use any extra properties in the shader, _UnusedTex is unsigned in Material references
    Code (csharp):
    1.  
    2. fixed4 frag (v2f i) : SV_Target
    3.             {
    4.                 // sample the texture
    5.                 //float usedAB = _UnusedFloatA * _UnusedFloatB;
    6.                 fixed4 col = tex2D(_MainTex, i.uv);I
    7.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA);              
    8.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA * _UnusedFloatB);
    9.                 return col;
    10.             }
    11.  
    Result: The compiled shader did not include references to _UnusedFloatA, _UnusedFloatB or _UnusedTex.

    #2. Include references to _UnusedFloatA and _UnusedFloatB in the shader but not in the final output, _UnusedTex is unsigned in Material references:
    Code (csharp):
    1.  
    2. fixed4 frag (v2f i) : SV_Target
    3.             {
    4.                 // sample the texture
    5.                 float usedAB = _UnusedFloatA * _UnusedFloatB;
    6.                 fixed4 col = tex2D(_MainTex, i.uv);I
    7.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA);              
    8.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA * _UnusedFloatB);
    9.                 return col;
    10.             }
    11.  
    Result: Exactly the same shader as #1

    #3. Don't use any extra properties in the shader, _UnusedTex has a texture assigned in Material references:
    Result: Exactly the same shader as #1

    #4. Use _UnusedFloatA but not _UnusedFloatB in final output:
    Code (csharp):
    1.  
    2. fixed4 frag (v2f i) : SV_Target
    3.             {
    4.                 // sample the texture
    5.                 //float usedAB = _UnusedFloatA * _UnusedFloatB;
    6.                 //fixed4 col = tex2D(_MainTex, i.uv);I
    7.                 fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA);              
    8.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA * _UnusedFloatB);
    9.                 return col;
    10.             }
    11.  
    Result: _UnusedFloatA is referenced in compiled shader

    #5. Use _UnusedFloatA and _UnusedFloatB in final output:
    Code (csharp):
    1.  
    2. fixed4 frag (v2f i) : SV_Target
    3.             {
    4.                 // sample the texture
    5.                 //float usedAB = _UnusedFloatA * _UnusedFloatB;
    6.                 //fixed4 col = tex2D(_MainTex, i.uv);I
    7.                 //fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA);              
    8.                 fixed4 col = tex2D(_MainTex, i.uv * _UnusedFloatA * _UnusedFloatB);
    9.                 return col;
    10.             }
    11.  
    Result: _UnusedFloatA and _UnusedFloatB are referenced in compiled shader