0

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

  • Check UI elements. It is a good tool to extend the editor. Here is an example https://www.youtube.com/watch?v=J2KNj3bw0Bw&ab_channel=Unity – Morion Mar 02 '23 at 07:41

1 Answers1

1

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

derHugo
  • 83,094
  • 9
  • 75
  • 115
  • Thanks a lot, i am trying to use `PropertyDrawer` in the `Data` class because i want to draw all `TextureSet` at once to make a `small` or `big` icon. But also having a issue: I have `public float scale = 1;` in my `TextureSet`, but it's 0 when i add a new element. Can this be fix? – SoMuch SMx Mar 04 '23 at 06:21
  • using a `ReorderableList` you can overwrite the default behavior for what to do if a new element is added and could apply default values there. Usually it should use your default values .. tbh not sure why this is not the case if it is in the list :/ – derHugo Mar 06 '23 at 08:24
  • What I would do in the case of have different icon sizes: Have one static implementation of the `OnGUI` called e.g. `Draw` and then pass in the `property` and additional the desired icon size => You can use the same method for a single property field / the default implementation but can also use it in a bigger custom `Editor` in order to draw the field with the custom size from within the editor for your `Data` – derHugo Mar 06 '23 at 08:25