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

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

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

871155-20170412132441611-1081204444.png
实现对应的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);
    }

最后效果如下
871155-20170412134139283-1581734167.png

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

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

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

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

转载于:https://www.cnblogs.com/yangrouchuan/p/6698844.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值