How to change the name of List elements in the inspector

Based on what I read here. Add a hidden name field. Update the elements of your array in OnValidate

    [Serializable]
    public class LessSimpleNode
    {
        [HideInInspector]
        public string name;

        public AudioClip clip;   
        public override string ToString()
        {
            if (clip == null)
                return "(no clip)";
            return clip.name;
        }
    }

    [SerializeField]
    private LessSimpleNode[] clips;

    private void OnValidate()
    {
        DopeArrayEditor(clips);
    }

    static void DopeArrayEditor<T>(System.Collections.Generic.IEnumerable<T> clips)
    {
        if (clips == null)
            return;
        var prop = typeof(T).GetField("name");
        if (prop == null)
            return;
        foreach (var n in clips)
            prop.SetValue(n, n.ToString());
    }

This is atrocious :smile:. @ButtCleaves has already a good approach, you can improve this further with serialization events:

public class SkillEntry : ISerializationCallbackReceiver
    {
        [HideInInspector]
        [SerializeField] private string name;
        [SerializeField] private EntitySkills _id;
     
        public void OnBeforeSerialize()
        {
            name = _id.ToString();
        }

        public void OnAfterDeserialize()
        {
            name = _id.ToString();
        }
    }

This way you can chose the name consistently based on your data. Don’t mind the Entity and Skill stuff, it’s just an excerpt from my current project. The important part is ISerializationCallbackReceiver and the name property.

8 Likes

Oh! Thanks,not all heroes wear capes!

Please create your own thread rather than hijacking an existing one. This is why we have threads with lots of different subjects being discussed.

Thanks.

sure thing!

1 Like

Yep, it’s as simple as putting a string named “name” in code. Thanks @Laparen

public string name;

Instead of using serialized values for names, I tried to add a dynamic solution. You just need to add an interface to the struct you want to name

using UnityEngine;

namespace Attributes
{
  public class ArrayElementTitleAttribute : PropertyAttribute
  {
    public string VarName { get; }

    public ArrayElementTitleAttribute(string elementTitleVar = "")
    {
      VarName = elementTitleVar;
    }
  }

  public interface IArrayElementTitle
  {
    public string Name
    {
      get;
    }
  }
}
using UnityEditor;
using UnityEngine;

namespace Attributes.Editor
{
  [CustomPropertyDrawer(typeof(ArrayElementTitleAttribute))]
  public class ArrayElementTitleAttributeDrawer : PropertyDrawer
  {
    SerializedProperty _titleNameProp;

    protected virtual ArrayElementTitleAttribute Attribute => (ArrayElementTitleAttribute)attribute;

    public override float GetPropertyHeight(SerializedProperty property,
                                            GUIContent label)
    {
      return EditorGUI.GetPropertyHeight(property, label, true);
    }

    public override void OnGUI(Rect position,
                               SerializedProperty property,
                               GUIContent label)
    {
      if (property.boxedValue is IArrayElementTitle titled)
      {
        label = new GUIContent(label) { text = titled.Name };
      }
      else
      {
        string fullPathName = property.propertyPath + "." + Attribute.VarName;
        SerializedProperty nameProp = property.serializedObject.FindProperty(fullPathName);
        if (nameProp != null)
          label = new GUIContent(label) { text = GetTitle(nameProp) };
        else
          Debug.LogWarning(
            $"Could not get name for property path {fullPathName}, did you define a path or inherit from IArrayElementTitle?");
      }

      EditorGUI.PropertyField(position, property, label, true);
    }

    string GetTitle(SerializedProperty prop)
    {
      switch (prop.propertyType)
      {
        case SerializedPropertyType.Generic:
          break;
        case SerializedPropertyType.Integer:
          return prop.intValue.ToString();
        case SerializedPropertyType.Boolean:
          return prop.boolValue.ToString();
        case SerializedPropertyType.Float:
          return prop.floatValue.ToString("G");
        case SerializedPropertyType.String:
          return prop.stringValue;
        case SerializedPropertyType.Color:
          return prop.colorValue.ToString();
        case SerializedPropertyType.ObjectReference:
          return prop.objectReferenceValue.ToString();
        case SerializedPropertyType.LayerMask:
          break;
        case SerializedPropertyType.Enum:
          return prop.enumNames[prop.enumValueIndex];
        case SerializedPropertyType.Vector2:
          return prop.vector2Value.ToString();
        case SerializedPropertyType.Vector3:
          return prop.vector3Value.ToString();
        case SerializedPropertyType.Vector4:
          return prop.vector4Value.ToString();
      }

      return "";
    }
  }
}

Example usage

using System;
using Attributes;
using UnityAtoms;

namespace Atoms.Preferences
{
  [Serializable]
  public struct AtomPreference: IArrayElementTitle
  {
    public string key;
    public AtomBaseVariable variable;
    public string Name => key;
  }
}
    [ArrayElementTitle] [SerializeField]
    AtomPreference[] preferences;
2 Likes

Awesome! Thank you very much.

Hi

Hi, I tested your code and it works great with the textures array variable, but when replace it with a string array I get a repeated error message in the console…

type is not a supported pptr value
UnityEditor.EditorGUI:ObjectField (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent)
NamedArrayDrawer:OnGUI (UnityEngine.Rect,UnityEditor.SerializedProperty,UnityEngine.GUIContent) (at Assets/Editor/NamedArrayDrawer.cs:10)
UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)

tried with float, integer and the error message keeps showing in the console, can you help please?

I have answered the same question in this thread!

1 Like

Found it, thanks a lot Baste :slight_smile:

you are amazing and saved my future you cannot know how long ı was searching for it I even thought buy odin inspector only for this. Although it works perfect it gives some invisible errors, ı did not see them until I try to saw yellow error also saw others since they dont give any error in editor and vs code says no issue found alsı game works fine. do you know any idea?
9551371--1349851--upload_2023-12-28_16-23-39.png

Fantastic thread. I was looking for a generic way to handle this in which the given serializable could simply call a method - like a GetDescriptor() - which they could override much like ToString(), but purely for array serialization.

Expanding on the work by @ButtCleaves and @notagame, I created this util class:

public abstract class ArraySerializable : ISerializationCallbackReceiver
{
    [HideInInspector][SerializeField] private string descriptor;

    protected abstract string GetDescriptor();

    public void OnBeforeSerialize()
    {
        descriptor = GetDescriptor();
    }

    public void OnAfterDeserialize() { }
}

This abstracts away all that nasty boilerplate and keeps the implementation light. Now I can extend from ArraySerializable for any class I want to display in an array:

[System.Serializable]
public class AnimationPair : ArraySerializable
{
    [SerializeField] private AnimationGroup group;
    public AnimationGroup Group { get => group; }

    [SerializeField] private Animation2D anim;
    public Animation2D Anim { get => anim; }

    protected override string GetDescriptor()
    {
        return group + " -> " + anim;
    }
}

Now for the end user (other programmers in your project), they can confidently apply any display string using GetDescriptor(), including conditional logic.

Here’s an example serializing an array of the above AnimationPair class:

public class AnimationSet2D : ScriptableObject
{
    [SerializeField] private AnimationPair[] animPairs;
    public AnimationPair[] AnimPairs { get => animPairs; }
}

1 Like

I would recommend using an interface instead of an abstract class, so you support structs or UnityEngine.Object

You do run into the issue of not being able to declare a custom drawer for the interface.

I totally agree with the premise of using an interface for something like this - and my solution will definitely have some limitations without it. But, unfortunately this entire solution is essentially relying on having that instance string field there at the top, so that Unity will read it as the display string automatically. Without a more explicit interface to change the display text from Unity’s end, I think we’re limited to the abstract class approach without taking a more heavyweight approach (of which there are lots of other examples in the thread!).

1 Like

Here’s how I tackled this problem: Create a custom PropertyDrawer with an invisible label; Unity would then automatically use that label as the List element name. This solves the problem with inheritance in the above reply, and the code is fairly simple.

public abstract class InvisibleLabelPropertyDrawer : PropertyDrawer
{
    protected static GUIStyle _invisibleStyle;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        _invisibleStyle ??= new GUIStyle(GUIStyle.none) { normal = { textColor = Color.clear } };
        EditorGUI.LabelField(position, GetLabelText(property), _invisibleStyle);
        EditorGUI.PropertyField(position, property, label, true);
    }

    protected abstract string GetLabelText(SerializedProperty property);

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        return EditorGUI.GetPropertyHeight(property);
    }
}

Then simply inherit from this class (put it in an Editor folder of course), and override the GetLabelText method. Like this:

[CustomPropertyDrawer(typeof(InventoryItem))]
public class InventoryItemDrawer : InvisibleLabelPropertyDrawer
{
    protected override string GetLabelText(SerializedProperty property)
    {
        var inventoryItem = (InventoryItem)property.boxedValue;
        return $"{inventoryItem.Item?.DisplayName ?? "Invalid Item"} ({inventoryItem.amount})";
    }
}

The result:

1 Like