Unity - 在检视面板中自定义绘制结构体

3

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
}

如果我将x、y和z变量设为非只读变量,它们会被很好地显示在Unity检查器中。然而,我有一些规则需要满足(实际上是 x+y+z=0),所以我加了只读来防止人们乱搞。

但是作为只读变量,它们不会被显示(因为它们无法修改)!:(

我想知道是否有办法可以像属性绘制器那样,在Unity检查器中显示它们。我知道我可以将我的结构体转换为类,因为属性绘制器是为类保留的,但我想保持它作为结构体。

那么,有没有办法显示这些值?并最终使用自定义初始化器修改它们?

非常感谢!

1个回答

6

readonly 使它们也 non-serialized,即不会在检查器中显示。

请注意PropertyDrawer 不仅限于 class 类型,还可用于 struct 类型。


实际上并不需要一个 CustomPropertyDrawer

您可以使用公共的只读属性来访问私有字段,并使用[SerializeField]将其显示在检查器中。这使它们仅可通过检查器进行编辑,而无法通过其他类进行编辑。

[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;
        }
    }
}

如果你真的想使用PropertyDrawer,除了在检查器中禁止编辑这些值并保存和查看它们之外,还可以添加如下代码:

[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
}

提示:在更改后检查值的方法,MonoBehaviour.OnValidate 可能对您有帮助。


关于 OnValidate() 的提示:它可能很有用,但会将逻辑移到每个 Monobehaviour 脚本中... 因此可能会导致大量重复代码,因为该结构相当常用。 - Eyap
1
@Eyap 因此,这只是一个提示 ;) 为了避免重复的代码,您可以在结构体中实现逻辑,并仅为每个MonoBehaviours字段调用它;) 否则,您将无法绕过编写自己的CustomPropertyDrawer - derHugo
这样做违背了readonly的目的,因为变量将在检查器中暴露出来以供编辑。即使OP接受了这个答案,我认为他的意思是像调试检查器中那样显示变量为灰色。 - Bip901
@Bip901,你肯定需要一个PropertyDrawer...既然OP接受了答案,我却不会认为这就是OP想要的...否则,如果它不能满足需求,为什么OP会接受答案呢?;)...无论如何,为了你,我添加了一个PropertyDrawer,可以额外完成这个任务。 - derHugo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接