通常來講,當咱們要擴展編輯器時,咱們會從Editor類繼承,爲本身的MonoBehaviour實現不一樣的外觀。
可是若是有一個struct/class,在許多地方被使用,Unity默認的外觀又不夠好看,此時想修改它的外觀,就須要使用PropertyDrawer了。html
上圖是一個Monobehaviour中包含一個簡單的struct(TileCoord類),包含兩個int,可是顯示效果十分別扭。c#
實現對應的PropertyDrawer後編輯器
相對於Editor類能夠修改MonoBehaviour的外觀,咱們能夠簡單的理解PropertyDrawer爲修改struct/class的外觀的Editor類。
實現上面的效果的代碼以下ide
[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一個方法來自定義高度,不然默認爲一行高。ui
property:待繪製的屬性自己。Unity在編輯器的API中大部分的實際的值都是用一個SerializedProperty表示的,實際上就是對值的一個包裝。經過這個包裝,當咱們修改值的時候,Unity能夠知道此次操做,相似刷新界面、Undo、prefab修改之類的信息均可以幫咱們處理好。壞處在於咱們得經過相似FindPropertyRelative的方法,用字符串去尋找內部的值(SerializedProperty是個嵌套結構,內部的數據也是SerializedProperty)。在Unity升級C#來支持nameof以前,咱們只能儘可能避免修改字段的名字了。同時,咱們繪製這些property的時候能夠直接用EditorGUI.PropertyField(property),而不用相似的 x = EditorGUI.IntField(x)這樣的調用。設計
label:這個值在MonoBehaviour裏的字段名。3d
另外,在PropertyDrawer中不能使用帶Layout的類,即EditorGUILayout、GUILayout。( http://answers.unity3d.com/questions/661360/finally-a-solution-cant-use-guilayout-stuff-in-pro.html )用了的話會報個迷之錯誤。不過彷佛並非bug,而是設計如此(不容許PropertyDrawer用Layout)。code
最後說一下EditorGUIUtility.labelWidth的使用。htm
我在最開始實現這個PropertyDrawer時,代碼以下blog
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寬度就變小了。繪製完以後調回去便可。