Search code examples
c#unity-game-engineunity-editorunity3d-editorunity3d-gui

How to custom the look of a class in a list/array in ScriptableObject in editor?


I have a ScriptableObject that simply have a list of object:

    class MyClassHolder : ScriptableObject
    {
        public MyClass[] myClass;
    }
    [System.Serializable]
    public class MyClass
    {
        public string key;
        public int priority;

        public Data data;
    }

And some class for datas:

    [System.Serializable]
    public class Data
    {
        public Icon icon;
        public Color color;
    }

    [System.Serializable]
    public class Icon
    {
        public TextureSet[] small;
        public TextureSet[] big;
    }

    [System.Serializable]
    public class TextureSet
    {
        public Texture2D texture;
        public Vector2 offset;
        [Range(0f, 1.2f)]
        public float scale = 1;
    }

Which now looking like this:

enter image description here

I have the code for drawing the icon which just like:

enter image description here

What i want to try to do is add a preview beside of the "Small" and "Big", like this if can:

Or under the Data like this:

enter image description here

I know there are CustomEditor or some Drawer but no idea which is suite for the case.

And Which class should i working with?

Update: I end up having this, looking good enter image description here


Solution

  • For this use case I wouldn't use a CustomEditor, which is for implementing a custom Inspector for an entire ScriptableObject or MonoBehaviour. This would be a lot of overhead just to customize the way a certain field is drawn. Additionally you would have to do it for each and every other class where you use this field type.

    You rather want a custom PropertyDrawer, which is used to implement a custom way for drawing only the field of a specific type - in your case for the TextureSet.

    Could look somewhat like e.g.

    ...
    #if UNITY_EDITOR
    using UnityEditor;
    #endif
    
    [Serializable]
    public class TextureSet
    {
        public Texture2D texture;
        public Vector2 offset;
        [UnityEngine.Range(0f, 1.2f)]
        public float scale = 1;
    
    #if UNITY_EDITOR
        [CustomPropertyDrawer(typeof(TextureSet))]
        private class TextureSetDrawer : PropertyDrawer
        {
            // height and width of the preview field in lines
            const float PREVIEW_SIZE_LINES = 4;
    
            // height and width of the preview field in pixels
            readonly float PREVIEW_SIZE = EditorGUIUtility.singleLineHeight * PREVIEW_SIZE_LINES;
    
            public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
            {
                if (!property.isExpanded)
                {
                    // if folded simply single line
                    return EditorGUIUtility.singleLineHeight;
                }
    
                // compose the total height of the poperty
                // 1 line - offset
                // 1 line - scale
                // PREVIEW_SIZE_LINES - preview
                // 1 line - a bit of buffer to separate from next list element
                return EditorGUIUtility.singleLineHeight * (2 + PREVIEW_SIZE_LINES + 1);
            }
    
            public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
            {
                using (new EditorGUI.PropertyScope(position, label, property))
                {
                    if (!property.isExpanded)
                    {
                        // draw the foldout label of the entire TextureSet property
                        property.isExpanded = EditorGUI.Foldout(position, property.isExpanded, label);
                    }
                    else
                    {
                        // move down half buffer to separate a bit from previous fields
                        position.y += EditorGUIUtility.singleLineHeight * 0.5f;
    
                        // draw the foldout label of the entire TextureSet property
                        property.isExpanded = EditorGUI.Foldout(new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight), property.isExpanded, label);
                        
                        // indent the child properties a bit for better visual grouping
                        using (new EditorGUI.IndentLevelScope())
                        {
                            position = EditorGUI.IndentedRect(position);
    
                            // Find/Bind the three properties
                            var textureProperty = property.FindPropertyRelative(nameof(texture));
                            var offsetProperty = property.FindPropertyRelative(nameof(offset));
                            var scaleProperty = property.FindPropertyRelative(nameof(scale));
    
                            // Calculate the positions and sizes of the fields to draw
                            var textureRect = new Rect(position.x, position.y + PREVIEW_SIZE * 0.5f - EditorGUIUtility.singleLineHeight * 0.5f, position.width - PREVIEW_SIZE, EditorGUIUtility.singleLineHeight);
                            var previewRect = new Rect(position.x + position.width - PREVIEW_SIZE, position.y, PREVIEW_SIZE, PREVIEW_SIZE);
                            var offsetRect = new Rect(position.x, position.y + previewRect.height, position.width, EditorGUIUtility.singleLineHeight);
                            var scaleRect = new Rect(position.x, offsetRect.y + EditorGUIUtility.singleLineHeight, position.width, EditorGUIUtility.singleLineHeight);
    
                            // The default texture field
                            EditorGUI.PropertyField(textureRect, textureProperty);
    
                            // using a grey texture as fallback if there is none referenced yet
                            var tex = textureProperty.objectReferenceValue ? (Texture)textureProperty.objectReferenceValue : Texture2D.grayTexture;
                            var texCoords = new Rect(offsetProperty.vector2Value.x, offsetProperty.vector2Value.y, 1 / scaleProperty.floatValue, 1 / scaleProperty.floatValue);
                            GUI.DrawTextureWithTexCoords(previewRect, tex, texCoords);
    
                            // The default vector2 field
                            EditorGUI.PropertyField(offsetRect, offsetProperty);
    
                            // The default float field with RangeAttribute applied
                            EditorGUI.PropertyField(scaleRect, scaleProperty);
                        }
                    }
                }
            }
        }
    #endif
    }
    

    I hope this is a good starting point, you will probably have to play around a bit and figure out how exactly you want to apply the scale and offset to the preview according to your needs.


    Little demo

    public class Example : MonoBehaviour
    {
        public TextureSet test;
        public TextureSet[] tests;
    }
    

    enter image description here