Search Unity

In editor when I change a variable, resize a array ?

Discussion in 'Scripting' started by mcunha98, Apr 4, 2014.

  1. mcunha98

    mcunha98

    Joined:
    Jun 13, 2010
    Posts:
    261
    Are there a way to link a variable in editor, and when this variable was changed I recreate a array list ?
    $arraylinked.png

    For example, my variable Quantidade Inimigos (enemy's quantity) is a matrix to reorganize the Array "Moedas".

    Actually I use an internal check in code to I don't forget to recreate the array if I change the quantity variable, but I guess there are a way to link it (remember that I need to do this in editor to put the values of integers for each enemy).
     
    Last edited: Apr 4, 2014
  2. bigmisterb

    bigmisterb

    Joined:
    Nov 6, 2010
    Posts:
    4,221
    If you want to use an array, Simply change the size of the array and read its length instead of this other number. Far simpler than reorganizing the editor.
     
  3. mcunha98

    mcunha98

    Joined:
    Jun 13, 2010
    Posts:
    261
    This is the problem.
    I know this, but I want to know if there is a way to "connect" the variables in editor to make my life more easy
     
  4. Deleted User

    Deleted User

    Guest

    Properties and custom inspector
    Code (csharp):
    1.  
    2.  
    3. public int[] meArray = new int[0];
    4.  
    5. // expose this to inspector via custom editor
    6. // see http://wiki.unity3d.com/index.php?title=Expose_properties_in_inspector
    7. public int MeArraySize
    8. {
    9.     get
    10.     {
    11.         return meArray.lenght;
    12.     }
    13.     set
    14.     {
    15.         meArray = new int[value];
    16.     }
    17. }
    18.  
    19.  
     
  5. mcunha98

    mcunha98

    Joined:
    Jun 13, 2010
    Posts:
    261
    Element

    Thanks for your time and advice, but not work yet.
    I don't know if the fact that I have a complex object (Inimigo.Items is a serializable class with bool/int) .


    Any idea ?
     
  6. bigdaddy

    bigdaddy

    Joined:
    May 24, 2011
    Posts:
    153
    This is not as straightforward as you would think. You need to write a custom inspector and change the moedas property arraySize value to change the size of the array. Sounds fine however the custom inspector code is run after every keystroke so if you want to change quantidadeInimigos from 6 to 9. As soon as you hit backspace to clear out the 6, your moedas array is erased. So here's some code I found a few years back - unfortunately I can't recall from whom.

    I put this code into a static class that holds my editor helpers:

    Code (csharp):
    1.  
    2. public static class AISEditorGUIUtils {
    3.  
    4.     public static string s_EditedValue = string.Empty;
    5.     public static string s_LastTooltip = string.Empty;
    6.     public static int s_EditedField = 0;
    7.    
    8.     /// <summary>
    9.     /// Creates an special IntField that only changes the actual value when pressing enter or losing focus
    10.     /// </summary>
    11.     /// <param name="label">The label of the int field</param>
    12.     /// <param name="value">The value of the intfield</param>
    13.     /// <returns>The valuefo the intfield</returns>
    14.     public static int ArraySizeField(string label, int value)
    15.     {
    16.         // Get current control id
    17.         int controlID = GUIUtility.GetControlID(FocusType.Passive);
    18.    
    19.         // Assign real value if out of focus or enter pressed,
    20.         // the edited value cannot be empty and the tooltip must match to the current control
    21.        
    22.         if ((controlID.ToString() == s_LastTooltip  s_EditedValue != string.Empty)
    23.             ((Event.current.Equals (Event.KeyboardEvent ("[enter]")) || Event.current.Equals (Event.KeyboardEvent ("return")) ||
    24.              Event.current.Equals (Event.KeyboardEvent ("tab")) || (Event.current.type == EventType.MouseDown))))
    25.         {
    26.  
    27.             // Draw textfield, somehow this makes it work better when pressing enter
    28.             // No idea why...
    29.             EditorGUILayout.BeginHorizontal();
    30.             s_EditedValue = EditorGUILayout.TextField(new GUIContent(label, controlID.ToString()), s_EditedValue, EditorStyles.numberField);
    31.             EditorGUILayout.EndHorizontal();
    32.    
    33.             // Parse number
    34.             int number = 0;
    35.             if (int.TryParse(s_EditedValue, out number))
    36.             {
    37.                 value = number;
    38.             }
    39.    
    40.             // Reset values, the edit value must go back to its original state
    41.             s_EditedValue = value.ToString();
    42.             s_EditedField = 0;
    43.             return value;
    44.         }
    45.         else
    46.         {
    47.             // Only draw this if the field is not being edited
    48.             if (s_EditedField != controlID)
    49.             {
    50.                 // Draw textfield with current original value
    51.                 EditorGUILayout.BeginHorizontal();
    52.                 EditorGUILayout.TextField(new GUIContent(label, controlID.ToString()), value.ToString(), EditorStyles.numberField);
    53.                 EditorGUILayout.EndHorizontal();
    54.    
    55.                 // Save last tooltip if gets focus... also save control id
    56.                 if (GUI.tooltip == controlID.ToString())
    57.                 {
    58.                     s_LastTooltip = GUI.tooltip;
    59.                     s_EditedField = controlID;
    60.                 }
    61.             }
    62.             else
    63.             {
    64.                 // Draw textfield, now with current edited value
    65.                 EditorGUILayout.BeginHorizontal();
    66.                 s_EditedValue = EditorGUILayout.TextField(new GUIContent(label, controlID.ToString()), s_EditedValue, EditorStyles.numberField);
    67.                 EditorGUILayout.EndHorizontal();
    68.             }
    69.         }
    70.    
    71.         return value;
    72.     }
    73. }
    74.  
    Now to use it. First I need to make a SerializedObject and SerializedProperties. First we define them
    Code (csharp):
    1.  
    2.     private SerializedObject group;
    3.     private SerializedProperty
    4.         members,    // this is an array
    5.         other, fields, here;
    6.  
    Then we need to load them up
    Code (csharp):
    1.  
    2. void OnEnable() {
    3.     group = new SerializedObject(target);
    4.    
    5.     members = group.FindProperty("members");  // load the array
    6.     other =
    7.     fields =
    8.     here =
    9. }
    10.  
    And now we use it
    Code (csharp):
    1.  
    2. public override void OnInspectorGUI() {
    3.     group.Update();
    4.  
    5.     int size = AISEditorGUIUtils.ArraySizeField("Group Size:", members.arraySize);
    6.     if (size != members.arraySize) {
    7.         members.arraySize = size;
    8.     }
    9.  
    Now the arraySize won't update until a valid integer is entered AND we've exited the field.

    So you would pass in quantidadeInimigos to ArraySizeField, then check against moedas.arraySize. Something like this
    Code (csharp):
    1.  
    2.     int size = AISEditorGUIUtils.ArraySizeField("Group Size:", quantidadeInimigos;
    3.     if (size != moedas.arraySize) {
    4.         moedas.arraySize = size;
    5.         quantidadeInimigos = size;
    6.     }
    7.  
    You may be able to use quantidadeInimigos directly instead of having a temporary size variable but I'm not 100% sure.

    Hope this helps
     
  7. scarffy

    scarffy

    Joined:
    Jan 15, 2013
    Posts:
    25
    Although this is old thread.
    I was looking for solution to the same problem and I think I found it.
    Just sharing so it benefit someone.

    Code (CSharp):
    1.  
    2.  
    3. public class Test_Data : MonoBehaviour {
    4.     /// <summary>
    5.     /// Resize array according to Total_Data
    6.     /// </summary>
    7.  
    8.     [Range(0,10)]
    9.     public int Total_Data;
    10.     public string[] Test_String;
    11.     public List<string> Test_String_List;
    12. }
    13.  
    Code (CSharp):
    1.  
    2.  
    3. using UnityEditor;
    4. [CustomEditor(typeof(Test_Data))]
    5. public class Test_Data_Editor : Editor {
    6.  
    7.     public override void OnInspectorGUI(){
    8.  
    9.         Test_Data test_Data = target as Test_Data;
    10.  
    11.         DrawDefaultInspector();
    12.  
    13.         test_Data.Test_String = new string[test_Data.Total_Data];
    14.         test_Data.Test_String_List = new List<string>(new string[test_Data.Total_Data]);
    15.     }
    16. }
    17.  
    edit : forgot about using UnityEditor;
     
    Last edited: Apr 30, 2019
    zeiksz likes this.
  8. zeiksz

    zeiksz

    Joined:
    Mar 10, 2019
    Posts:
    10
    Thank you! This helped in my project.
    In hope to help too to a random stranger I give an idea I struggled with: I used this method for having a scriptable object as object database, and making a class as an editor of that. To keep it simple: the scriptable object had two lists and a refresh method public. One list was the full list of items. Other list was the debug list like "weapons". The refresh method made a linq for this debug list, like "weapons = full.Where(szűrve => szűrve is weaponclass).Cast<weaponclass>().ToArray();", then the editor "OnInspectorGUI" override calls this refresh method.

    Have a nice life!
     
  9. Cris_2013

    Cris_2013

    Joined:
    Nov 25, 2017
    Posts:
    39
    I came across this problem and just realized that there is a delayed field function implemented in the EditorGUILayout that achieves this.

    So the code would be as simple as:
    Code (CSharp):
    1. EditorGUI.BeginChangeCheck();
    2. arraySize = EditorGUILayout.DelayedIntField(arraySize);
    3. if (EditorGUI.EndChangeCheck())
    4. {
    5.          ChangeArraySize(arraySize);
    6. }

    (my version is Uniy 2019.2)
     
    dunlepop and mpn7 like this.
  10. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,325
    I still think that going the route that @element_wsc suggested is the most elegant solution, and it saves you from having to write a new custom inspector every time you want to do something similar to this.

    There are solutions out there that allow you to easily expose properties in the inspector, one of them being my own asset Power Inspector. Once you have such a solution in your project, you can simply do this:

    Code (CSharp):
    1. public int[] array = new int[0];
    2.  
    3. [ShowInInspector]
    4. public int ArraySize
    5. {
    6.     get
    7.     {
    8.         return array.Length;
    9.     }
    10.  
    11.     set
    12.     {
    13.     Array.Resize(ref array, value);
    14.     }
    15. }
     
  11. Deleted User

    Deleted User

    Guest

  12. dev_cwj

    dev_cwj

    Joined:
    Aug 31, 2018
    Posts:
    3
    Why they don't use OnValidate() ? (or they don't know that)

    Code (CSharp):
    1.  
    2.  
    3. public int enemyLength = 5;
    4. public int[] enemyArray;
    5.  
    6. void Reset()
    7. {
    8.     enemyArray = new int[enemyLength];
    9. }
    10.  
    11. void OnValidate()
    12. {
    13.     if (enemyArray.Length != enemyLength)
    14.     {
    15.         Array.Resize(ref enemyArray, enemyLength);
    16.     }
    17. }
    18.  
    19.  
     
  13. dev_cwj

    dev_cwj

    Joined:
    Aug 31, 2018
    Posts:
    3
    Or you can make custom attribute for the role of value-change-detector.

    Code (CSharp):
    1.  
    2.  
    3. void OnEnemyLengthChanged()
    4. {
    5.     if (enemyArray.Length != enemyLength)
    6.         Array.Resize(ref enemyArray, enemyLength);
    7. }
    8.  
    9. [OnValueChanged(nameof(OnEnemyLengthChanged))]
    10. public int enemyLength = 5;
    11.  
    12. void OnEnemyArrayChanged()
    13. {
    14.     if (enemyArray.Length != enemyLength)
    15.         enemyLength = enemyArray.Length;
    16. }
    17.  
    18. [OnValueChanged(nameof(OnEnemyArrayChanged))]
    19. public int[] enemyArray;
    20.  
    21.  
    And use like this.
     
    Wilhelm_LAS likes this.