Search code examples
c#unity-game-engineunity-editor

Unity Simple Property Drawer or Popup to display custom object-array scriptable object


I'm still quite new to unity and c# and trying to replicate a seemingly simple custom editor and property drawer for data i'm preparing via scriptable object. In this case a class to use multiple tags on a gameObject, to identify what is what quickly when lots of them are detected by a sensor. I'm on that since way too long and questioning my sanity because it can't be that hard. I'm just lacking some rather basic knowledge/understanding, i think. The whole concept around SerializedProperty and the handling of it is very unintuitive for me.

Found this handy code-snippet here to create LayerMasks containing multiple layers:

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(SingleUnityLayer))]
public class SingleUnityLayerPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label)
    {
        EditorGUI.BeginProperty(_position, GUIContent.none, _property);
        SerializedProperty layerIndex = _property.FindPropertyRelative("m_LayerIndex");
        _position = EditorGUI.PrefixLabel(_position, GUIUtility.GetControlID(FocusType.Passive), _label);
        if (layerIndex != null)
        {
            layerIndex.intValue = EditorGUI.LayerField(_position, layerIndex.intValue);
        }
        EditorGUI.EndProperty();
    }
}

which works off this class

using UnityEngine;

[System.Serializable]
public class SingleUnityLayer {
    [SerializeField]
    private int m_LayerIndex = 0;
    private string m_LayerName = "";

    public int LayerIndex {
        get { return m_LayerIndex; }
    }

    public string LayerName {
        get { return m_LayerName; }
    }

    public void Set(int _layerIndex) {
        if(_layerIndex > 0 && _layerIndex < 32) {
            m_LayerIndex = _layerIndex;
            m_LayerName = LayerMask.LayerToName(m_LayerIndex);
        }
    }

    public int Mask {
        get { return 1 << m_LayerIndex; }
    }
}

and creates this result, which is great:

enter image description here

Now: I want to have the same thing, showing an array of a custom tags scriptable object class or even a simple string[] if necessary but can't get it to work. The property field for the drawer would be something like public Tag[] tags; where the Tag class simply contains a public name property for the moment.

I don't even have the code of my many attempts because it got messy and i kinda gave up and i found some solutions online which i didnt' even try because they seemed way to complex to be necessary for that simple task.

Can someone please push me in the right direction. A little more than "read up on custom editors" would be amazing ;)

Thanks

(not really the topic here but if someone can tell me a better(cheap) way to identify colliders detected with an overlapcircle than with tags, please do tell ;)

(hope the code-blocks and stuff work out..first post)

Edit: After helpful input from @derHugo who understood better what i want than myself, i came up with this simple solution:

[CustomPropertyDrawer(typeof(SingleUnityTag))]
public class SingleUnityTagPropertyDrawer : PropertyDrawer {
    //string selectedTag = "";
    public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label) {
        EditorGUI.BeginProperty(_position, GUIContent.none, _property);
        SerializedProperty tagIndex = _property.FindPropertyRelative("m_TagIndex");
        SerializedProperty tagName = _property.FindPropertyRelative("m_TagName");
        _position = EditorGUI.PrefixLabel(_position, GUIUtility.GetControlID(FocusType.Passive), _label);
        if(tagIndex != null) {
            tagName.stringValue = EditorGUI.TagField(_position, tagName.stringValue);
        }
        EditorGUI.EndProperty();
    }
}


Solution

  • So if I understand correctly now what you actually want is not a ScriptableObject at all but rather have a custom tag selection dropdown.

    Unfortunately there isn't something built-in for this but you can create one using EditorGUI.TagField like e.g.

        using System;
        using UnityEngine;
    #if UNITY_EDITOR
        using System.Linq;
        using UnityEditor;
    #endif
    
        public class TagAttribute : PropertyAttribute
        {
    #if UNITY_EDITOR
            [CustomPropertyDrawer(typeof(TagAttribute))]
            private class TagAttributeDrawer : PropertyDrawer
            {
                public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
                {
                    return EditorGUIUtility.singleLineHeight;
                }
    
                public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
                {
                    EditorGUI.BeginProperty(position, label, property);
    
                    if (property.propertyType != SerializedPropertyType.String)
                    {
                        EditorGUI.HelpBox(position, $"{nameof(TagAttribute)} can only be used for strings!", MessageType.Error);
                        return;
                    }
    
                    if (!UnityEditorInternal.InternalEditorUtility.tags.Contains(property.stringValue))
                    {
                        property.stringValue = "";
                    }
    
                    var color = GUI.color;
                    if (string.IsNullOrWhiteSpace(property.stringValue))
                    {
                        GUI.color = Color.red;
                    }
    
                    property.stringValue = EditorGUI.TagField(position, label, property.stringValue);
                    GUI.color = color;
    
                    EditorGUI.EndProperty();
                }
            }
    #endif
        }
    

    so in your components you can now simply have a

    [Tag] public string tag;
    

    or

    [Tag] public string[] tags;