• [Unity]用PropertyDrawer自定义struct/class的外观


    一般来说,当我们要扩展编辑器时,我们会从Editor类继承,为自己的MonoBehaviour实现不同的外观。
    但是如果有一个struct/class,在许多地方被使用,Unity默认的外观又不够好看,此时想修改它的外观,就需要使用PropertyDrawer了。


    上图是一个Monobehaviour中包含一个简单的struct(TileCoord类),包含两个int,但是显示效果十分别扭。


    实现对应的PropertyDrawer后

    相对于Editor类可以修改MonoBehaviour的外观,我们可以简单的理解PropertyDrawer为修改struct/class的外观的Editor类。
    实现上面的效果的代码如下

    [CustomPropertyDrawer(typeof(TileCoord))]
    public class TileCoordEditor : PropertyDrawer {
        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
            var x = property.FindPropertyRelative("x");
            var y = property.FindPropertyRelative("y");
            float LabelWidth = EditorGUIUtility.labelWidth;
            var labelRect = new Rect(position.x, position.y, LabelWidth, position.height);
            var xRect = new Rect(position.x + LabelWidth, position.y, (position.width - LabelWidth) / 2 - 20, position.height);
            var yRect = new Rect(position.x + LabelWidth + (position.width - LabelWidth) / 2 - 20 , position.y, (position.width - LabelWidth) / 2 - 20, position.height);
            
            EditorGUIUtility.labelWidth = 12.0f;
            EditorGUI.LabelField(labelRect, label);
            EditorGUI.PropertyField(xRect, x);
            EditorGUI.PropertyField(yRect, y);
            EditorGUIUtility.labelWidth = LabelWidth;
        }
    //需要自定义高度
      //  public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
      //      return 
      //  }
    }
    

    可以看到跟继承Editor的操作很相似。不过额外在OnGUI提供了三个参数,依次解释一下:
    position:该属性在Editor中被分配到的位置、大小。注意这里的x,y对应的是左上角,跟游戏中的左下角不同(因为Inspector是从上到下绘制)。大小的宽度由Inspector的宽度决定,而高度需要通过在类中override一个方法来自定义高度,否则默认为一行高。

    property:待绘制的属性本身。Unity在编辑器的API中大部分的实际的值都是用一个SerializedProperty表示的,实际上就是对值的一个包装。通过这个包装,当我们修改值的时候,Unity可以知道这次操作,类似刷新界面、Undo、prefab修改之类的信息都可以帮我们处理好。坏处在于我们得通过类似FindPropertyRelative的方法,用字符串去寻找内部的值(SerializedProperty是个嵌套结构,内部的数据也是SerializedProperty)。在Unity升级C#来支持nameof之前,我们只能尽量避免修改字段的名字了。同时,我们绘制这些property的时候可以直接用EditorGUI.PropertyField(property),而不用类似的 x = EditorGUI.IntField(x)这样的调用。

    label:这个值在MonoBehaviour里的字段名。

    另外,在PropertyDrawer中不能使用带Layout的类,即EditorGUILayout、GUILayout。( http://answers.unity3d.com/questions/661360/finally-a-solution-cant-use-guilayout-stuff-in-pro.html )用了的话会报个迷之错误。不过似乎并不是bug,而是设计如此(不允许PropertyDrawer用Layout)。

    最后说一下EditorGUIUtility.labelWidth的使用。

    我在最开始实现这个PropertyDrawer时,代码如下

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
            var x = property.FindPropertyRelative("x");
            var y = property.FindPropertyRelative("y");
            float LabelWidth = 50;
            var labelRect = new Rect(position.x, position.y, LabelWidth, position.height);
            var xRect = new Rect(position.x + LabelWidth, position.y, (position.width - LabelWidth) / 2 , position.height);
            var yRect = new Rect(position.x + LabelWidth + (position.width - LabelWidth) / 2  , position.y, (position.width - LabelWidth) / 2 , position.height);
            
            EditorGUI.LabelField(labelRect, label);
            EditorGUI.PropertyField(xRect, x);
            EditorGUI.PropertyField(yRect, y);
        }
    

    最后效果如下

    可以看到,int的输入框被两个label挤到了右边。而我想要的效果是类似Box Collider2D里的那种样式。

    而我们用PropertyField绘制的时候,并没有设置Label宽度的办法。

    随后找到资料,发现EditorGUIUtility.labelWidth这个属性。代表的是Label的宽度。比较奇葩的是它是一个可写的属性,修改之后,之后绘制的label的宽度就变成了写进去的值了。不得不说,包括indentLevel在内,这些API设计的都很有想法。

    最后解决办法就是在PropertyField绘制之前,先把labelWidth改小,这样绘制出来的PropertyField前面的Label宽度就变小了。绘制完之后调回去即可。

  • 相关阅读:
    [JavaScript]继续学习DOM事件模型
    [面试]作答整理一些面试题
    [JavaScript]使用jQuery实现无缝轮播
    [JavaScript]使用CSS + jQuery 实现自动轮播图
    [JavaScript]从DOM到jQuery(2)
    [JavaScript]从DOM到jQuery(1)
    [JavaScript]JavaScript中的函数(2)
    [JavaScript]JavaScript中的函数(1)
    [JavaScript]JavaScript中的Array
    React练习 7 :点击div,显示innerHTML
  • 原文地址:https://www.cnblogs.com/yangrouchuan/p/6698844.html
Copyright © 2020-2023  润新知