简体   繁体   中英

Unity - Custom drawing of a struct in the inspector

I have a custom struct, with the following code :

[Serializable]
public struct HexPoint : IEquatable<HexPoint>
{
    public readonly int x;
    public readonly int y;
    public readonly int z;
    
    // Some custom methods for initializations and operators
}

If I make the x, y, and z variables non-readonly, they are displayed in the unity inspector just fine. However, I have some rules that they need to satisfy (actually x+y+z=0), so I added readonly to prevent people to mess with it.

But as readonly variables, they are not displayed (as they can't be modified)! :(

I was wondering if they are a way for me to display them in the unity inspector, with something similar to a PropertyDrawer. I know that I could switch my struct to a class, as PropertyDrawer is reserved for classes, but I'd like to keep it as a struct.

So, is there a way to display the values? And eventually to modify them using the custom initializers?

Thanks a lot!

readonly makes them also non-serialized -> not displayed in the Inspector

Note that PropertyDrawer is not limited to class types but can also be used for struct types.


There is actually no need for a CustomPropertyDrawer .

You can have public readonly properties to access private fields and to display them in the Inspector use [SerializeField] which makes them editable only via the Inspector but not via other classes.

[Serializable]
public struct HexPoint : IEquatable<HexPoint>
{
    // Those are not displayed in the inspector, 
    // readonly and accessible by other classes
    public int x { get { return _x; } }
    public int y { get { return _y; } }
    public int z { get { return _z; } }

    // if you prefer you can also use the expression body style instead
    //public int x => _x;
    //public int y => _y;
    //public int z => _z;

    // Those are displayed and editable in the Inspector
    // but private and therefor not changeable by other classes
    [SerializeField] private int _x;
    [SerializeField] private int _y;
    [SerializeField] private int _z;

    public bool Equals(HexPoint other)
    {
        return _x == other._x && _y == other._y && _z == other._z;
    }

    public override bool Equals(object obj)
    {
        return obj is HexPoint other && Equals(other);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = _x;
            hashCode = (hashCode * 397) ^ _y;
            hashCode = (hashCode * 397) ^ _z;
            return hashCode;
        }
    }
}

If you really want to use a PropertyDrawer to additionally also disallow to edit these values in the Inspector but still save and see them you could add one like eg

[Serializable]
public struct HexPoint : IEquatable<HexPoint>
{
    // Those are not displayed in the inspector, 
    // readonly and accessible by other classes
    public int x { get { return _x; } }
    public int y { get { return _y; } }
    public int z { get { return _z; } }

    // if you prefer you can also use the expression body style instead
    //public int x => _x;
    //public int y => _y;
    //public int z => _z;

    // Those are displayed and editable in the Inspector
    // but private and therefor not changeable by other classes
    [SerializeField] private int _x;
    [SerializeField] private int _y;
    [SerializeField] private int _z;

    public bool Equals(HexPoint other)
    {
        return _x == other._x && _y == other._y && _z == other._z;
    }

    public override bool Equals(object obj)
    {
        return obj is HexPoint other && Equals(other);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = _x;
            hashCode = (hashCode * 397) ^ _y;
            hashCode = (hashCode * 397) ^ _z;
            return hashCode;
        }
    }

#if UNITY_EDITOR

    [CustomPropertyDrawer(typeof(HexPoint))]
    public class HexPointDrawer : PropertyDrawer
    {
        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
                return EditorGUIUtility.singleLineHeight * (EditorGUIUtility.wideMode ? 1 : 2);
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            // Find the SerializedProperties by name
            var x = property.FindPropertyRelative(nameof(_x));
            var y = property.FindPropertyRelative(nameof(_y));
            var z = property.FindPropertyRelative(nameof(_z));

            // Using BeginProperty / EndProperty on the parent property means that
            // prefab override logic works on the entire property.
            EditorGUI.BeginProperty(position, label, property);
            {
                // Makes the fields disabled / grayed out
                EditorGUI.BeginDisabledGroup(true);
                {
                    // In your case the best option would be a Vector3Field which handles the correct drawing
                    EditorGUI.Vector3IntField(position, label, new Vector3Int(x.intValue, y.intValue, z.intValue));
                }
                EditorGUI.EndDisabledGroup();
            }
            EditorGUI.EndProperty();
        }
    }

#endif
}

Hint for checking the values after a change MonoBehaviour.OnValidate might be interresting for you

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM