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;
}
}
}
Another (not documented) option is also to use serialized auto-properties
[field: SerializeField] public int x { get; private set; }
[field: SerializeField] public int y { get; private set; }
[field: SerializeField] public int z { get; private set; }
Note though that should you ever require a custom editor or property drawer for these the name of an automatically generated backing field will not be e.g. x
but rather "<x>k__BackingField"
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 e.g.
[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