Search Unity

  1. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Let us know a bit about your interests, and if you'd like to become more directly involved. Take our survey!
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  5. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

[SOLVED] PropertyDrawer on Array elements with different heights?

Discussion in 'Extensions & OnGUI' started by Eldoir, Jul 25, 2018.

  1. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    23
    Hello there,
    I've been struggling with this for an hour now.

    I have an array of GrassTexture elements,
    And I want each GrassTexture to be drawn with a PropertyDrawer.
    There is a variable in GrassTexture, "useDifferentSettings", that shows other variables of the class if its toggle is checked.
    So, I will have an array of GrassTextures, and each GrassTexture will have its own height, depending on this toggle.

    But I've read Unity uses only ONE instance of the drawer to draw all my elements, so I guess this is a problem for me.
    I've overriden GetPropertyHeight, but it seems it can only return a single value for every element, because of that "one instance" thing.

    Let's say I have 3 elements in my array, respectively of height 34, 50 and 86 (depending on boolean values, for example).
    It seems my PropertyDrawer will draw each element with a height of 86, which is the height of the last element.

    Some code and screens below:

    My Drawer:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomPropertyDrawer(typeof(GrassTexture))]
    5. public class GrassTextureDrawer : PropertyDrawer
    6. {
    7.     private Rect position;
    8.     private const int lineHeight = 16;
    9.     private int totalPropertyHeight = 0;
    10.     private int marginBetweenFields;
    11.  
    12.  
    13.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    14.     {
    15.         this.position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), GUIContent.none);
    16.         this.position.height = lineHeight;
    17.         totalPropertyHeight = lineHeight;
    18.  
    19.         marginBetweenFields = (int)EditorGUIUtility.standardVerticalSpacing;
    20.  
    21.         SerializedProperty newTexture = property.FindPropertyRelative("newTexture");
    22.         SerializedProperty useDifferentSettings = property.FindPropertyRelative("useDifferentSettings");
    23.         SerializedProperty minWidth = property.FindPropertyRelative("minWidth");
    24.         SerializedProperty maxWidth = property.FindPropertyRelative("maxWidth");
    25.         SerializedProperty minHeight = property.FindPropertyRelative("minHeight");
    26.         SerializedProperty maxHeight = property.FindPropertyRelative("maxHeight");
    27.         SerializedProperty noiseSpread = property.FindPropertyRelative("noiseSpread");
    28.         SerializedProperty healthyColor = property.FindPropertyRelative("healthyColor");
    29.         SerializedProperty dryColor = property.FindPropertyRelative("dryColor");
    30.         SerializedProperty billboard = property.FindPropertyRelative("billboard");
    31.  
    32.         int indentWidth = 15 * EditorGUI.indentLevel;
    33.         this.position.x -= indentWidth;
    34.         this.position.width += indentWidth;
    35.  
    36.         DisplayPropertyField(newTexture);
    37.         DisplayPropertyField(useDifferentSettings, spaceBelow: false);
    38.  
    39.         if (useDifferentSettings.boolValue)
    40.         {
    41.             AddToPositionY(lineHeight + 8);
    42.  
    43.             DisplayPropertyField(minWidth);
    44.             DisplayPropertyField(maxWidth);
    45.             DisplayPropertyField(minHeight);
    46.             DisplayPropertyField(maxHeight);
    47.             DisplayPropertyField(noiseSpread);
    48.             DisplayPropertyField(healthyColor);
    49.             DisplayPropertyField(dryColor);
    50.             DisplayPropertyField(billboard, spaceBelow: false);
    51.         }
    52.     }
    53.  
    54.     private void DisplayPropertyField(SerializedProperty property, bool spaceBelow = true)
    55.     {
    56.         EditorGUI.PropertyField(position, property);
    57.  
    58.         if (spaceBelow)
    59.         {
    60.             AddToPositionY(lineHeight + marginBetweenFields);
    61.         }
    62.     }
    63.  
    64.     private void AddToPositionY(int addY)
    65.     {
    66.         position.y += addY;
    67.         totalPropertyHeight += addY;
    68.     }
    69.  
    70.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    71.     {
    72.         return totalPropertyHeight;
    73.     }
    74. }
    My GrassTexture class:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [System.Serializable]
    4. public class GrassTexture
    5. {
    6.     [SerializeField]
    7.     private Texture2D newTexture = null;
    8.  
    9.     [SerializeField]
    10.     private bool useDifferentSettings;
    11.  
    12.     [SerializeField]
    13.     private float minWidth = 0.5f;
    14.  
    15.     [SerializeField]
    16.     private float maxWidth = 1f;
    17.  
    18.     [SerializeField]
    19.     private float minHeight = 0.5f;
    20.  
    21.     [SerializeField]
    22.     private float maxHeight = 1f;
    23.  
    24.     [SerializeField]
    25.     private float noiseSpread = 0.1f;
    26.  
    27.     [SerializeField]
    28.     private Color healthyColor = Color.gray;
    29.  
    30.     [SerializeField]
    31.     private Color dryColor = Color.yellow;
    32.  
    33.     [SerializeField]
    34.     private bool billboard = true;
    35. }
     

    Attached Files:

  2. Estecka

    Estecka

    Joined:
    Oct 11, 2013
    Posts:
    59
    I think your use of `GetPropertyHeigt()` is wrong.

    The way it works, for each time the propertydrawer is drawn, `GetPropertyHeight` is called BEFORE `OnGUI`. But the way you you built it, you are calculating the height in OnGUI (too late), and then have GetPropertyHeight return it.

    As a result, your GetPropertyHeigh is returning the height of the previously drawn property instead of the current one.

    In order to fix that, you need `GetPropertyHeight()` to search for the relevant propertyrelatives and calculate the height there, instead of relying on OnGUI
     
  3. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    23
    Hello Estecka,
    Thanks for your reply! :)
    I tried what you suggested with the following code.
    Basically, I just count the SerializedProperties I have to display, in order to calculate the height dynamically this time.
    The fix is dirty, as I just wanted to test if it worked.
    And it does not, unfortunately :( What did I do wrong?
    P.S : I'm sorry for the long lines of code, I didn't find any option to show/hide it.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomPropertyDrawer(typeof(GrassTexture))]
    5. public class GrassTextureDrawer : PropertyDrawer
    6. {
    7.     private Rect position;
    8.     private const int lineHeight = 16;
    9.     private int totalPropertyHeight = 0;
    10.     private int marginBetweenFields;
    11.  
    12.     SerializedProperty newTexture, useDifferentSettings, minWidth,
    13.      maxWidth, minHeight, maxHeight, noiseSpread, healthyColor, dryColor, billboard;
    14.  
    15.  
    16.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    17.     {
    18.         this.position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), GUIContent.none);
    19.         this.position.height = lineHeight;
    20.         totalPropertyHeight = lineHeight;
    21.  
    22.         marginBetweenFields = (int)EditorGUIUtility.standardVerticalSpacing;
    23.  
    24.         newTexture = property.FindPropertyRelative("newTexture");
    25.         useDifferentSettings = property.FindPropertyRelative("useDifferentSettings");
    26.         minWidth = property.FindPropertyRelative("minWidth");
    27.         maxWidth = property.FindPropertyRelative("maxWidth");
    28.         minHeight = property.FindPropertyRelative("minHeight");
    29.         maxHeight = property.FindPropertyRelative("maxHeight");
    30.         noiseSpread = property.FindPropertyRelative("noiseSpread");
    31.         healthyColor = property.FindPropertyRelative("healthyColor");
    32.         dryColor = property.FindPropertyRelative("dryColor");
    33.         billboard = property.FindPropertyRelative("billboard");
    34.  
    35.         int indentWidth = 15 * EditorGUI.indentLevel;
    36.         this.position.x -= indentWidth;
    37.         this.position.width += indentWidth;
    38.  
    39.         DisplayPropertyField(newTexture);
    40.         DisplayPropertyField(useDifferentSettings, spaceBelow: false);
    41.  
    42.         if (useDifferentSettings.boolValue)
    43.         {
    44.             AddToPositionY(lineHeight + 8);
    45.  
    46.             DisplayPropertyField(minWidth);
    47.             DisplayPropertyField(maxWidth);
    48.             DisplayPropertyField(minHeight);
    49.             DisplayPropertyField(maxHeight);
    50.             DisplayPropertyField(noiseSpread);
    51.             DisplayPropertyField(healthyColor);
    52.             DisplayPropertyField(dryColor);
    53.             DisplayPropertyField(billboard, spaceBelow: false);
    54.         }
    55.     }
    56.  
    57.     private float GetHeight()
    58.     {
    59.         return (lineHeight + marginBetweenFields) * (useDifferentSettings.boolValue ? 11 : 3);
    60.     }
    61.  
    62.     private void DisplayPropertyField(SerializedProperty property, bool spaceBelow = true)
    63.     {
    64.         EditorGUI.PropertyField(position, property);
    65.  
    66.         if (spaceBelow)
    67.         {
    68.             AddToPositionY(lineHeight + marginBetweenFields);
    69.         }
    70.     }
    71.  
    72.     private void AddToPositionY(int addY)
    73.     {
    74.         position.y += addY;
    75.         totalPropertyHeight += addY;
    76.     }
    77.  
    78.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    79.     {
    80.         if (useDifferentSettings == null) return 0f;
    81.         return GetHeight();
    82.     }
    83. }
     
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    308
    You aren't setting useDifferentSettings.
     
  5. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    23
    Hello @brownboot67,
    Thanks for your reply :)
    In fact, since Estecka told me that GetPropertyHeight was called before OnGUI, I simply made a nullcheck at line 80, and I was thinking that the next time the drawer would call GetPropertyHeight, useDifferentSettings would have been initialized in OnGUI, and I could be able to call GetHeight().
    Unfortunately, that last code I posted has the same result as my first try.
     
  6. Estecka

    Estecka

    Joined:
    Oct 11, 2013
    Posts:
    59
    You still have the exact same problem as before in the new code; GetPropertyHeight is still using data from the previous draw instead of the current one :
    You're using a SerializedProperty to figure out the height, but this SerializedProperty is still assigned in OnGUI -after the relevant GetPropertyHeight has been called.

    I've only read your code in diagonal, but I think a quick fix would be along these lines. :
    Code (CSharp):
    1.  public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    2.     {
    3.         useDifferentSettings = property.FindPropertyRelative("useDifferentSettings") // Assign it here, not then
    4.         return GetHeight();
    5.     }
    The bottom line is : GetPropertyHeight should not use anything that is assigned in OnGUI, have GetPropertyHeight assign everything by itself.
     
    Last edited: Jul 27, 2018
    Eldoir likes this.
  7. Eldoir

    Eldoir

    Joined:
    Feb 27, 2015
    Posts:
    23