How to change the name of List elements in the inspector

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:
2904353--213892--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.

1 Like

If You create your own script to contain the texture then you can place in your own name variable.

//An Array or List of a custom class like this will have a name variable for you to change
[System.Serializable]//makes sure this shows up in the inspector
public class TextureContain {
    public string name;//your name variable to edit
    public Texture tex;//place texture in here
}

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?

5 Likes

For the giggles, create an attribute:

using UnityEngine;

public class NamedArrayAttribute : PropertyAttribute
{
    public readonly string[] names;
    public NamedArrayAttribute(string[] names) { this.names = names; }
}

Create a PropertyDrawer (make sure this is in an ‘editor’ folder):

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer (typeof(NamedArrayAttribute))]public class NamedArrayDrawer : PropertyDrawer
{
    public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
    {
        try {
            int pos = int.Parse(property.propertyPath.Split('[', ']')[1]);
            EditorGUI.ObjectField(rect, property, new GUIContent(((NamedArrayAttribute)attribute).names[pos]));
        } catch {
            EditorGUI.ObjectField(rect, property, label);
        }
    }
}

Usage:

[NamedArrayAttribute (new string[] {"Neutral", "Happy", "Sad"})]
public Texture[] textures;
18 Likes

@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:

[System.Serializeable]
public struct FaceTextures {
    public Texture neutral;
    public Texture happy;
    public Texture sad;
    ...
}

And instead of doing this:

public Texture[] faces;
...
SetFace(faces[1]);

You do this:

public FaceTextures faces;
...
SetFace(faces.happy);

Then you don’t have to remember which index is what.

15 Likes

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.

public struct k
{
public string key;
}
public k[] Keys;

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

public class ArrayElementTitleAttribute : PropertyAttribute
{
    public string Varname;
    public ArrayElementTitleAttribute(string ElementTitleVar)
    {
        Varname = ElementTitleVar;
    }
}

Drawer:

[CustomPropertyDrawer(typeof(ArrayElementTitleAttribute))]
public class ArrayElementTitleDrawer : PropertyDrawer
{
    public override float GetPropertyHeight(SerializedProperty property,
                                    GUIContent label)
    {
        return EditorGUI.GetPropertyHeight(property, label, true);
    }
    protected virtual ArrayElementTitleAttribute Atribute
    {
        get { return (ArrayElementTitleAttribute)attribute; }
    }
    SerializedProperty TitleNameProp;
    public override void OnGUI(Rect position,
                              SerializedProperty property,
                              GUIContent label)
    {
        string FullPathName = property.propertyPath + "." + Atribute.Varname;
        TitleNameProp = property.serializedObject.FindProperty(FullPathName);
        string newlabel = GetTitle();
        if (string.IsNullOrEmpty(newlabel))
            newlabel = label.text;
        EditorGUI.PropertyField(position, property, new GUIContent(newlabel, label.tooltip), true);
    }
    private string GetTitle()
    {
        switch (TitleNameProp.propertyType)
        {
            case SerializedPropertyType.Generic:
                break;
            case SerializedPropertyType.Integer:
                return TitleNameProp.intValue.ToString();
            case SerializedPropertyType.Boolean:
                return TitleNameProp.boolValue.ToString();
            case SerializedPropertyType.Float:
                return TitleNameProp.floatValue.ToString();
            case SerializedPropertyType.String:
                return TitleNameProp.stringValue;
            case SerializedPropertyType.Color:
                return TitleNameProp.colorValue.ToString();
            case SerializedPropertyType.ObjectReference:
                return TitleNameProp.objectReferenceValue.ToString();
            case SerializedPropertyType.LayerMask:
                break;
            case SerializedPropertyType.Enum:
                return TitleNameProp.enumNames[TitleNameProp.enumValueIndex];
            case SerializedPropertyType.Vector2:
                return TitleNameProp.vector2Value.ToString();
            case SerializedPropertyType.Vector3:
                return TitleNameProp.vector3Value.ToString();
            case SerializedPropertyType.Vector4:
                return TitleNameProp.vector4Value.ToString();
            case SerializedPropertyType.Rect:
                break;
            case SerializedPropertyType.ArraySize:
                break;
            case SerializedPropertyType.Character:
                break;
            case SerializedPropertyType.AnimationCurve:
                break;
            case SerializedPropertyType.Bounds:
                break;
            case SerializedPropertyType.Gradient:
                break;
            case SerializedPropertyType.Quaternion:
                break;
            default:
                break;
        }
        return "";
    }
}

example

    [System.Serializable]
    public struct MyStruct
    {
        public enum MyEnum { hello, world }
        public MyEnum m_MyEnum;
    }
    [ArrayElementTitle("m_MyEnum")]
    public MyStruct[] m_MyStruct;

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

39 Likes

@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.

4 Likes

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

[hideininspector] public string SlotName.

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.

2 Likes

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:

[System.Serializable]
public class WeaponsList {

    public string name;                            // Inspector Element Name
    public GameObject weaponGameObject;            // Weapon's physical GameObject
    public string weaponName;                    // Weapon name
    public string weaponDescription;            // Weapon description
    public int weaponLevel;                        // Weapon level
    public bool isRangedWeapon;                    // Is this a ranged weapon? (true=ranged; false=melee)
    public bool isExplosive;                    // Is this an explosive weapon?
    public float weaponRange;                    // Weapon range
    public float weaponDamage;                    // Weapon damage
    public float weaponFireRate;                // Weapon rate of fire

    public WeaponsList(GameObject newWeapon, string newName, string newDescription, int newLevel, bool newIsRangedWeapon, bool newIsExplosive,
        float newWeaponRange, float newWeaponDamage, float newWeaponFireRate)
    {
        weaponGameObject = newWeapon;
        weaponName = newName;
        weaponDescription = newDescription;
        weaponLevel = newLevel;
        isRangedWeapon = newIsRangedWeapon;
        isExplosive = newIsExplosive;
        weaponRange = newWeaponRange;
        weaponDamage = newWeaponDamage;
        weaponFireRate = newWeaponFireRate;
    }

}

… 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:

3302791--256241--Capture.PNG

public class Master_Inventory : MonoBehaviour {

    public static Master_Inventory Instance = null;

    public List<WeaponsList> m_WeaponsList = new List<WeaponsList> ();
    public List<ResourcesList> m_ResourcesList = new List<ResourcesList> ();

    void Awake()
    {
        Instance = this;
    }
}
15 Likes

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.

1 Like

@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:

[CustomPropertyDrawer (typeof (LocalizationItem))]
public class NamedArrayDrawer : PropertyDrawer {

   public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
      SerializedProperty key = property.FindPropertyRelative ("key");
      SerializedProperty value = property.FindPropertyRelative ("value");
      GUIContent enumLabel = new GUIContent (key.enumDisplayNames[key.enumValueIndex]);

      EditorGUI.BeginProperty (position, label, property);
      position = EditorGUI.PrefixLabel (position, GUIUtility.GetControlID (FocusType.Passive), enumLabel);
      var indent = EditorGUI.indentLevel;
      EditorGUI.indentLevel = 0;

      // Calculate rects
      var unitRect = new Rect(position.x, position.y, position.width, position.height);
      // Draw fields - passs GUIContent.none to each so they are drawn without labels
      EditorGUI.PropertyField(unitRect, value.stringValue);

      // Set indent back to what it was
      EditorGUI.indentLevel = indent;

      EditorGUI.EndProperty();
   }
}

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:

using UnityEngine;

#if UNITY_EDITOR
using System;
using UnityEditor;
#endif

// Defines an attribute that makes the array use enum values as labels.
// Use like this:
//      [NamedArray(typeof(eDirection))] public GameObject[] m_Directions;

public class NamedArrayAttribute : PropertyAttribute {
    public Type TargetEnum;
    public NamedArrayAttribute(Type TargetEnum) {
        this.TargetEnum = TargetEnum;
    }
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(NamedArrayAttribute))]
public class NamedArrayDrawer : PropertyDrawer {
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
        // Properly configure height for expanded contents.
        return EditorGUI.GetPropertyHeight(property, label, property.isExpanded);
    }
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
        // Replace label with enum name if possible.
        try {
            var config = attribute as NamedArrayAttribute;
            var enum_names = System.Enum.GetNames(config.TargetEnum);
            int pos = int.Parse(property.propertyPath.Split('[', ']')[1]);
            var enum_label = enum_names.GetValue(pos) as string;
            // Make names nicer to read (but won't exactly match enum definition).
            enum_label = ObjectNames.NicifyVariableName(enum_label.ToLower());
            label = new GUIContent(enum_label);
        } catch {
            // keep default label
        }
        EditorGUI.PropertyField(position, property, label, property.isExpanded);
    }
}
#endif
8 Likes

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.

1 Like

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.

    [NamedArrayAttribute (new string[] {"Neutral", "Happy", "Sad"})]
    public Color[] colors;

3730240--309016--upload_2018-9-28_16-8-57.png

3 Likes

It should be a PropertyField rather than an ObjectField, to support all kinds of properties.

8 Likes

Thank you!

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:

 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Replace label with enum name if possible.
        try
        {
            var config = attribute as NamedArrayAttribute;
            var enum_names = Enum.GetNames(config.TargetEnum);

            var match = Regex.Match(property.propertyPath, "\\[(\\d)\\]", RegexOptions.RightToLeft);
            int pos = int.Parse(match.Groups[1].Value);

            // Make names nicer to read (but won't exactly match enum definition).
            var enum_label = ObjectNames.NicifyVariableName(enum_names[pos].ToLower());
            label = new GUIContent(enum_label);
        }
        catch
        {
            // keep default label
        }
        EditorGUI.PropertyField(position, property, label, property.isExpanded);
    }

This works for nested objects having serialized arrays, each array having their own NamedArrayAttribute.

2 Likes

What would be great is if the struct/class itself could provide the name, like this:

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…

9 Likes

Exactly my thoughts as well!
I did how ever find a workaround which allows something simular to this.
For my inventory system, I wanted something that looks like this…
7488056--921707--unity-inspector.png
My solution for this looks as follows…

[System.Serializable]
public class Stack
{
    [HideInInspector]
    public string name;
    public ItemBase item;
    public int amount;

    public void Validate()
    {
        name = (item) ? item.DisplayName + " (x" + amount + ")" : "No item";
    }
}

Then call Validate from any available event source.
OnValidate, OnDrawGizmosSelected, etc…

PS: If you want to do this with structs, you will have to assign the modified struct back to the correct index. As they are not updated by reference, like a class.

8 Likes

Hey, i just came up with a simple trick for adding custom array names

[System.Serializable]
    public class WeaponInfo
    {
        [HideInInspector]
        public string name;
        public CommonConstants.GunID gunID;
        public GameObject gunRigPrefab;
    }

    public WeaponInfo[] enemyWeaponInfos;

    private void OnDrawGizmosSelected() {
        //NOW JUST SET THE NAMES TO WHATEVER YOU WANT
        for (int i = 0; i < enemyWeaponInfos.Length; ++i) {
            enemyWeaponInfos[i].name = enemyWeaponInfos[i].gunID.ToString();
        }
    }

Well, i probably don’t recommend using this if you have more than a hundred instances in the scene.

Thanks @vapgames and @Ledii , made a nice mesh of your ideas here. This is the result:

A custom class to hold a Scriptable Object (MaterialSO) and a amount of them:

    [Serializable]
    public class MaterialStack
    {
        [HideInInspector]
        public string name; // used used for the inspector list name, same as the MaterialSO.title
        public MaterialSO material;
        public byte amount;

        public MaterialStack(byte _amount, MaterialSO _material)
        {
            amount = _amount;
            material = _material;
        }
    }

And then another Scriptable Object called a Recipe with the Unity OnValidate function, looping through each Material and adding the amount to the hidden name string:

    [CreateAssetMenu(fileName = " Material", menuName = "Game Name/Recipe", order = 1)]
    public class RecipeSO : ScriptableObject
    {
        public string title;
        public List<MaterialStack> materials;

        private void OnValidate()
        {
            foreach (var t in materials)
            {
                t.name = t.material ? t.material.title + " (x" + t.amount + ")" : "No item";
            }
        }
    }
6 Likes