Search Unity

  1. Get the latest news, tutorials and offers directly to your inbox with our newsletters. Sign up now.
    Dismiss Notice

How to change the name of List elements in the inspector

Discussion in 'Scripting' started by Collin_Patrick, Jan 1, 2017.

  1. Collin_Patrick

    Collin_Patrick

    Joined:
    Sep 24, 2016
    Posts:
    83
    As the title suggests, I want to change the "Element 0, Element 1..." to something meaningful. I have tried looking around the internet for answers but they don't tell me what I want specifically. I want to be able to change the element names in the script that creates the list/array rather than renaming them directly in the inspector.

    Example:
    Capture.PNG

    I want to rename the elements to: Neutral, Sad, Happy, etc. so the facial expression textures are consistent between all characters and allow me to reference a specific expression with the same number across all characters. Also to be able to have the elements renamed by script and not manually each time would allow the images to be placed in the correct slot without having to reference similar assets.

    As always help is very much appreciated.
     
  2. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    897
    If You create your own script to contain the texture then you can place in your own name variable.
    Code (CSharp):
    1. //An Array or List of a custom class like this will have a name variable for you to change
    2. [System.Serializable]//makes sure this shows up in the inspector
    3. public class TextureContain {
    4.     public string name;//your name variable to edit
    5.     public Texture tex;//place texture in here
    6. }
    This however, will cause the array or list to have a secondary drop down to reveal the name and texture, which you may feel is more of a hassle than what you currently have.

    What's wrong with just having your Texture name be your desired name for it?
     
    Bubblehead333 likes this.
  3. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    4,979
    For the giggles, create an attribute:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class NamedArrayAttribute : PropertyAttribute
    5. {
    6.     public readonly string[] names;
    7.     public NamedArrayAttribute(string[] names) { this.names = names; }
    8. }
    9.  
    Create a PropertyDrawer (make sure this is in an 'editor' folder):
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. [CustomPropertyDrawer (typeof(NamedArrayAttribute))]public class NamedArrayDrawer : PropertyDrawer
    6. {
    7.     public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
    8.     {
    9.         try {
    10.             int pos = int.Parse(property.propertyPath.Split('[', ']')[1]);
    11.             EditorGUI.ObjectField(rect, property, new GUIContent(((NamedArrayAttribute)attribute).names[pos]));
    12.         } catch {
    13.             EditorGUI.ObjectField(rect, property, label);
    14.         }
    15.     }
    16. }
    Usage:

    Code (csharp):
    1. [NamedArrayAttribute (new string[] {"Neutral", "Happy", "Sad"})]
    2. public Texture[] textures;
     
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    5,418
    @JohnnyA's suggestion would work. I suggest, however, that you don't use an array for this!

    Instead, make a class/struct (depending on use case) that wraps the faces. So:

    Code (csharp):
    1. [System.Serializeable]
    2. public struct FaceTextures {
    3.     public Texture neutral;
    4.     public Texture happy;
    5.     public Texture sad;
    6.     ...
    7. }
    And instead of doing this:

    Code (csharp):
    1. public Texture[] faces;
    2. ...
    3. SetFace(faces[1]);
    You do this:

    Code (csharp):
    1. public FaceTextures faces;
    2. ...
    3. SetFace(faces.happy);
    Then you don't have to remember which index is what.
     
  5. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    308
    No, No, no!

    these are all pretty usless. There are Key (string) variable like "Title" or "Key" in unity where if these are set inside the array object (as below) the element will be named to the value of this variable.


    Code (csharp):
    1.  
    2. public struct k
    3. {
    4. public string key;
    5. }
    6. public k[] Keys;
    7.  
    8.  

    However, this has two limitations, firstly the name has to be Key or Title (there may be others names), and the other limitation is it has to be a string.

    The solution to this is to make an attribute which lets you pick which variable inside the object will be the name of the element.
    Attribute
    Code (csharp):
    1.  
    2. public class ArrayElementTitleAttribute : PropertyAttribute
    3. {
    4.     public string Varname;
    5.     public ArrayElementTitleAttribute(string ElementTitleVar)
    6.     {
    7.         Varname = ElementTitleVar;
    8.     }
    9. }
    10.  

    Drawer:
    Code (csharp):
    1.  
    2. [CustomPropertyDrawer(typeof(ArrayElementTitleAttribute))]
    3. public class ArrayElementTitleDrawer : PropertyDrawer
    4. {
    5.     public override float GetPropertyHeight(SerializedProperty property,
    6.                                     GUIContent label)
    7.     {
    8.         return EditorGUI.GetPropertyHeight(property, label, true);
    9.     }
    10.     protected virtual ArrayElementTitleAttribute Atribute
    11.     {
    12.         get { return (ArrayElementTitleAttribute)attribute; }
    13.     }
    14.     SerializedProperty TitleNameProp;
    15.     public override void OnGUI(Rect position,
    16.                               SerializedProperty property,
    17.                               GUIContent label)
    18.     {
    19.         string FullPathName = property.propertyPath + "." + Atribute.Varname;
    20.         TitleNameProp = property.serializedObject.FindProperty(FullPathName);
    21.         string newlabel = GetTitle();
    22.         if (string.IsNullOrEmpty(newlabel))
    23.             newlabel = label.text;
    24.         EditorGUI.PropertyField(position, property, new GUIContent(newlabel, label.tooltip), true);
    25.     }
    26.     private string GetTitle()
    27.     {
    28.         switch (TitleNameProp.propertyType)
    29.         {
    30.             case SerializedPropertyType.Generic:
    31.                 break;
    32.             case SerializedPropertyType.Integer:
    33.                 return TitleNameProp.intValue.ToString();
    34.             case SerializedPropertyType.Boolean:
    35.                 return TitleNameProp.boolValue.ToString();
    36.             case SerializedPropertyType.Float:
    37.                 return TitleNameProp.floatValue.ToString();
    38.             case SerializedPropertyType.String:
    39.                 return TitleNameProp.stringValue;
    40.             case SerializedPropertyType.Color:
    41.                 return TitleNameProp.colorValue.ToString();
    42.             case SerializedPropertyType.ObjectReference:
    43.                 return TitleNameProp.objectReferenceValue.ToString();
    44.             case SerializedPropertyType.LayerMask:
    45.                 break;
    46.             case SerializedPropertyType.Enum:
    47.                 return TitleNameProp.enumNames[TitleNameProp.enumValueIndex];
    48.             case SerializedPropertyType.Vector2:
    49.                 return TitleNameProp.vector2Value.ToString();
    50.             case SerializedPropertyType.Vector3:
    51.                 return TitleNameProp.vector3Value.ToString();
    52.             case SerializedPropertyType.Vector4:
    53.                 return TitleNameProp.vector4Value.ToString();
    54.             case SerializedPropertyType.Rect:
    55.                 break;
    56.             case SerializedPropertyType.ArraySize:
    57.                 break;
    58.             case SerializedPropertyType.Character:
    59.                 break;
    60.             case SerializedPropertyType.AnimationCurve:
    61.                 break;
    62.             case SerializedPropertyType.Bounds:
    63.                 break;
    64.             case SerializedPropertyType.Gradient:
    65.                 break;
    66.             case SerializedPropertyType.Quaternion:
    67.                 break;
    68.             default:
    69.                 break;
    70.         }
    71.         return "";
    72.     }
    73. }
    74.  


    example

    Code (csharp):
    1.  
    2.     [System.Serializable]
    3.     public struct MyStruct
    4.     {
    5.         public enum MyEnum { hello, world }
    6.         public MyEnum m_MyEnum;
    7.     }
    8.     [ArrayElementTitle("m_MyEnum")]
    9.     public MyStruct[] m_MyStruct;
    10.  


    Note: there is no error handling for a incorrect variable name
    Note: this will take the string value (ToString) of most 5.5 variable types, ones I left out I didn't think made much sense, or would need additional formatting which you can do if you wish
     
  6. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    4,979
    @BinaryCats the OP wanted to be able to name the 'slots' in an array. i.e slot one is always the 'happy' slot, slot two always the 'neutral' slot, etc.

    You may find this of questionable utility (to which @Baste provided the more typical solution), but solving a completely different problem doesn't really contribute to the discussion.
     
    GrayedFox likes this.
  7. BinaryCats

    BinaryCats

    Joined:
    Feb 8, 2016
    Posts:
    308
    This functionality is a all over solution to the same problem :- not being able to name array elements. Whether the field is hidden in inspector is up to you.

    He could for example, have a

    Code (csharp):
    1.  
    2. [hideininspector] public string SlotName.
    3.  
    Set that variable to what ever he likes "slots 1, slots2" and have the element in the array be called that. Solving a specific problem only helps one person, solving the actual problem helps everyone. Seen as this thread is the top thread that shows when googling, someone can use the attribute I provided to solve the problem.


    Again, to be clear, the real problem here is not being able to name elements what you would like them to be called, unless you use a custom editor.
     
  8. sdclark79

    sdclark79

    Joined:
    Feb 6, 2017
    Posts:
    21
    I'd say your answer is a custom List, with 'public string name;' at the top, which will set the name of the Element0 to whatever the name string is. For example:
    Code (CSharp):
    1.  
    2. [System.Serializable]
    3. public class WeaponsList {
    4.  
    5.     public string name;                            // Inspector Element Name
    6.     public GameObject weaponGameObject;            // Weapon's physical GameObject
    7.     public string weaponName;                    // Weapon name
    8.     public string weaponDescription;            // Weapon description
    9.     public int weaponLevel;                        // Weapon level
    10.     public bool isRangedWeapon;                    // Is this a ranged weapon? (true=ranged; false=melee)
    11.     public bool isExplosive;                    // Is this an explosive weapon?
    12.     public float weaponRange;                    // Weapon range
    13.     public float weaponDamage;                    // Weapon damage
    14.     public float weaponFireRate;                // Weapon rate of fire
    15.  
    16.     public WeaponsList(GameObject newWeapon, string newName, string newDescription, int newLevel, bool newIsRangedWeapon, bool newIsExplosive,
    17.         float newWeaponRange, float newWeaponDamage, float newWeaponFireRate)
    18.     {
    19.         weaponGameObject = newWeapon;
    20.         weaponName = newName;
    21.         weaponDescription = newDescription;
    22.         weaponLevel = newLevel;
    23.         isRangedWeapon = newIsRangedWeapon;
    24.         isExplosive = newIsExplosive;
    25.         weaponRange = newWeaponRange;
    26.         weaponDamage = newWeaponDamage;
    27.         weaponFireRate = newWeaponFireRate;
    28.     }
    29.  
    30. }
    ... and you could definitely programmatically set the name, but your super-awesome custom List class'll look like this in the Inspector, with Element0 changed to whatever's in Name:

    Capture.PNG

    Code (CSharp):
    1.  
    2. public class Master_Inventory : MonoBehaviour {
    3.  
    4.     public static Master_Inventory Instance = null;
    5.  
    6.     public List<WeaponsList> m_WeaponsList = new List<WeaponsList> ();
    7.     public List<ResourcesList> m_ResourcesList = new List<ResourcesList> ();
    8.  
    9.     void Awake()
    10.     {
    11.         Instance = this;
    12.     }
    13. }
    14.  
     
    Last edited: Nov 28, 2017
  9. Dsiak

    Dsiak

    Joined:
    Oct 11, 2015
    Posts:
    11
    Wonderful solutions, but I find JohnnyA's to be more practical for my case.

    Mine is a list of floats, and the index are enumerators, so it's somehow similar to the struct "face.happy" example, but the call would be face[(int)FaceTextures.happy]. The call is very a little ugly, but I have to iterate through those elements a lot and I'm more comfortable with a array for that.

    Another downside to using array is that the attribute's "new string[]" have to be compile-time constant, with means you have to manually write the name after creating a new enum / can't dynamically create a string[] with all the names of your enum's enums. This gets old pretty fast so do consider a struct / class.
     
  10. sand_lantern

    sand_lantern

    Joined:
    Sep 15, 2017
    Posts:
    175
    @Dsiak I found a way to clean up an array of (enum, value) pairs quite a bit, you don't need to worry about creating a string[] or any of that jazz. Check this out:

    Code (CSharp):
    1.  
    2. [CustomPropertyDrawer (typeof (LocalizationItem))]
    3. public class NamedArrayDrawer : PropertyDrawer {
    4.  
    5.    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    6.       SerializedProperty key = property.FindPropertyRelative ("key");
    7.       SerializedProperty value = property.FindPropertyRelative ("value");
    8.       GUIContent enumLabel = new GUIContent (key.enumDisplayNames[key.enumValueIndex]);
    9.  
    10.       EditorGUI.BeginProperty (position, label, property);
    11.       position = EditorGUI.PrefixLabel (position, GUIUtility.GetControlID (FocusType.Passive), enumLabel);
    12.       var indent = EditorGUI.indentLevel;
    13.       EditorGUI.indentLevel = 0;
    14.  
    15.       // Calculate rects
    16.       var unitRect = new Rect(position.x, position.y, position.width, position.height);
    17.       // Draw fields - passs GUIContent.none to each so they are drawn without labels
    18.       EditorGUI.PropertyField(unitRect, value.stringValue);
    19.  
    20.       // Set indent back to what it was
    21.       EditorGUI.indentLevel = indent;
    22.  
    23.       EditorGUI.EndProperty();
    24.    }
    25. }
     
  11. idbrii

    idbrii

    Joined:
    Aug 18, 2014
    Posts:
    48
    I'd agree with Baste in general, but when you are indexing the array with an enum and iterating over the array values, you really need an array. sand_lantern's solution didn't work for me.

    But thanks to JohnnyA's starter code, I figured out how to do this with an Attribute and PropertyDrawer:

    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. #if UNITY_EDITOR
    5. using System;
    6. using UnityEditor;
    7. #endif
    8.  
    9. // Defines an attribute that makes the array use enum values as labels.
    10. // Use like this:
    11. //      [NamedArray(typeof(eDirection))] public GameObject[] m_Directions;
    12.  
    13. public class NamedArrayAttribute : PropertyAttribute {
    14.     public Type TargetEnum;
    15.     public NamedArrayAttribute(Type TargetEnum) {
    16.         this.TargetEnum = TargetEnum;
    17.     }
    18. }
    19.  
    20. #if UNITY_EDITOR
    21. [CustomPropertyDrawer(typeof(NamedArrayAttribute))]
    22. public class NamedArrayDrawer : PropertyDrawer {
    23.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    24.         // Properly configure height for expanded contents.
    25.         return EditorGUI.GetPropertyHeight(property, label, property.isExpanded);
    26.     }
    27.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    28.         // Replace label with enum name if possible.
    29.         try {
    30.             var config = attribute as NamedArrayAttribute;
    31.             var enum_names = System.Enum.GetNames(config.TargetEnum);
    32.             int pos = int.Parse(property.propertyPath.Split('[', ']')[1]);
    33.             var enum_label = enum_names.GetValue(pos) as string;
    34.             // Make names nicer to read (but won't exactly match enum definition).
    35.             enum_label = ObjectNames.NicifyVariableName(enum_label.ToLower());
    36.             label = new GUIContent(enum_label);
    37.         } catch {
    38.             // keep default label
    39.         }
    40.         EditorGUI.PropertyField(position, property, label, property.isExpanded);
    41.     }
    42. }
    43. #endif
    44.  
     
  12. jj_unity328

    jj_unity328

    Joined:
    Jun 7, 2018
    Posts:
    22
    If you just need Unity to NOT use the first string it finds as the name of that element (which might be totally unrelated or misleading) you can put a `[HideInInspector] public string dummy;` as first element in your serialized class.
     
  13. MerovingL

    MerovingL

    Joined:
    Sep 19, 2016
    Posts:
    11
    I used
    Very interesting and usefull thread.
    I used this sollution, and it works fine with textures.
    But when I made array of colors, it doesn't work.

    Code (csharp):
    1.  
    2.     [NamedArrayAttribute (new string[] {"Neutral", "Happy", "Sad"})]
    3.     public Color[] colors;
    4.  
    upload_2018-9-28_16-8-57.png
     
    Last edited: Sep 28, 2018
    adityaspt, DrivZone and Luc36 like this.
  14. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    5,418
    It should be a PropertyField rather than an ObjectField, to support all kinds of properties.
     
    cdr9042, DrivZone, Luc36 and 2 others like this.
  15. MerovingL

    MerovingL

    Joined:
    Sep 19, 2016
    Posts:
    11
    Thank you!
     
  16. Ardenian

    Ardenian

    Joined:
    Dec 7, 2016
    Posts:
    313
    After finding this thread, I fell in love with this attribute! It is a very neat enhancement. However, I stumbled upon the problem that it doesn't work with nested arrays. If you have a serialized array of a custom class, which also has a serialized array, with both having their own
    NamedArrayAttribute
    , it leads to odd behavior because of the assumptions behind the line
    int pos = int.Parse(property.propertyPath.Split('[', ']')[1]);
    in the attribute drawer.

    Such a path could look like
    modifiers.containers.Array.data[0].containers.Array.data[0]
    and adding a
    NamedArrayAttribute
    to both arrays fails to replace the array element label properly. I modified the implementation for
    OnGUI()
    by @idbrii as it follows, adding
    using System.Text.RegularExpressions;
    to the using directives:

    Code (CSharp):
    1.  public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    2.     {
    3.         // Replace label with enum name if possible.
    4.         try
    5.         {
    6.             var config = attribute as NamedArrayAttribute;
    7.             var enum_names = Enum.GetNames(config.TargetEnum);
    8.  
    9.             var match = Regex.Match(property.propertyPath, "\\[(\\d)\\]", RegexOptions.RightToLeft);
    10.             int pos = int.Parse(match.Groups[1].Value);
    11.  
    12.             // Make names nicer to read (but won't exactly match enum definition).
    13.             var enum_label = ObjectNames.NicifyVariableName(enum_names[pos].ToLower());
    14.             label = new GUIContent(enum_label);
    15.         }
    16.         catch
    17.         {
    18.             // keep default label
    19.         }
    20.         EditorGUI.PropertyField(position, property, label, property.isExpanded);
    21.     }
    This works for nested objects having serialized arrays, each array having their own
    NamedArrayAttribute
    .
     
    JoaoBorks likes this.
  17. thebarryman

    thebarryman

    Joined:
    Nov 22, 2012
    Posts:
    76
    What would be great is if the struct/class itself could provide the name, like this:

    Code (CSharp):
    1. string name => otherProperty.ToString();
    Or alternatively, use the value returned by the type's ToString() method. Instead "name" has to be a serialized field or requires a custom PropertyDrawer. Sigh...
     
    Last edited: Jul 21, 2020
unityunity