一般来说,当我们要扩展编辑器时,我们会从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宽度就变小了。绘制完之后调回去即可。

[Unity]用PropertyDrawer自定义struct/class的外观的更多相关文章

  1. Unity 编辑器扩展自定义窗体

    这次看见Unity还可以自定义弹出窗体,让我很好奇.于是就去网上找文章看了看. 如果想自定义窗体需要把类放入Editor文件夹下面. 代码如下: using UnityEngine; using Un ...

  2. 自定义QT窗口部件外观之QStyle

    自定义QT窗口部件外观 重新定义Qt内置窗口部件的外观常用的方法有两种:一是通过子类化QStyle 类或者预定义的一个样式,例如QWindowStyle,来定制应用程序的观感:二是使用Qt样式表. Q ...

  3. 【Unity Shader】自定义材质面板的小技巧

    写在前面 之前遇到过一些朋友问怎么在材质面板里定义类似于bool这种变量,控制一些代码的执行.我们当然可以写一个C#文件来自定义材质面板,就像Unity为Standard Shader写材质面板一样( ...

  4. Unity 扩展属性自定义绘制

    这么晚了准备睡觉的时候,去学习了一会. 发现一个标题好奇的点进去. 居然是自定义绘制属性.  在前几天这个问题把我难住了,没想到几分钟就能解决的问题. 我花了半天时间使用反射去解决...  如果我们想 ...

  5. Unity编辑器:自定义编辑器样式——GUIStyle

    通过GUIStyle,可以自定义Unity编辑器的样式. GUIStyle可以new一个全新的实例,这样,需要自己处理所有自己需要的效果. GUIStyle还可以基于已经存在的实例new一个新的实例, ...

  6. golang自定义struct字段标签

    原文链接: https://sosedoff.com/2016/07/16/golang-struct-tags.html struct是golang中最常使用的变量类型之一,几乎每个地方都有使用,从 ...

  7. 2、以自定义struct或struct指针作为map的Key

    若干问题: struct Node { int k, b; friend bool operator <(Node a, Node b) { return a.k < b.k; } }no ...

  8. unity中使用自定义shader进行光照贴图烘培无法出现透明度的坑爹问题

    最近开发中在对场景进行光照贴图烘焙时发现一个坑爹问题,在使用自定义shader的时候,shader命名中必须包含Transparent路径,否则烘焙的时候不对alpha通道进行计算,烘焙出来都是狗皮膏 ...

  9. 【Android】9.3 自定义列表视图的外观

    分类:C#.Android.VS2015: 创建日期:2016-02-18 一.简介 自定义的列表视图通常用Resources/Layout文件夹下的axml文件中的资源来声明,适配器则通过Id去加载 ...

随机推荐

  1. ACM 第十二天

    博弈论(巴什博奕,威佐夫博弈,尼姆博弈,斐波那契博弈,SG函数,SG定理) 一.  巴什博奕(Bash Game): A和B一块报数,每人每次报最少1个,最多报4个,看谁先报到30.这应该是最古老的关 ...

  2. iOS开发解决页面滑动返回跟scrollView左右划冲突

    -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithG ...

  3. TCP系列16—重传—6、基础快速重传(Fast Retransmit)

    一.快速重传介绍 按照TCP协议,RTO超时重传是一个非常重要的事件,当RTO超时的时候,TCP会同时通过两种方式非常谨慎的降低发送数据包的速率,一种是基于拥塞控制削减发送窗口的大小,另外一个是通过指 ...

  4. C# 如何在winform中嵌入Excel,内嵌Excel,word

    近使用.net做一个小软件遇到一个问题,就是想实现把excel表格在winform中打开,同时可以操作,不单单是打开.或者就提取数据.在网上找了好多资料,发现这方面的资料比较少,即使有,都是旧版本的使 ...

  5. 深入学习 Redis系列

    深入学习 Redis(1):Redis 内存模型 深入学习 Redis(2):持久化 深入学习 Redis(3):主从复制 深入学习 Redis(4):哨兵

  6. 【bzoj3772】精神污染 STL+LCA+主席树

    题目描述 兵库县位于日本列岛的中央位置,北临日本海,南面濑户内海直通太平洋,中央部位是森林和山地,与拥有关西机场的大阪府比邻而居,是关西地区面积最大的县,是集经济和文化于一体的一大地区,是日本西部门户 ...

  7. CSS3 过渡效果触发时机的问题

    像360deg过渡效果,做专题的同学应该是经常做的. <!DOCTYPE html> <html lang="en"> <head> <m ...

  8. Gevent-自动挡切换

    Gevent: Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程. G ...

  9. 推荐算法相关总结表(包括DM)

    推荐算法总结表 表1 推荐算法分类 个性化推荐算法分类 启发式算法 基于模型 基于内容 TF-IDF 聚类 最大熵 相似度度量 贝叶斯分类 决策树 神经网络 专家系统 知识推理 协同过滤 K近邻 聚类 ...

  10. 122. Best Time to Buy and Sell Stock II (Array)

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...