This is atrocious . @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.
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;
}
}
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?
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?
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.
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; }
}
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!).
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})";
}
}