[Unity]用PropertyDrawer自定義struct/class的外觀

通常來講,當咱們要擴展編輯器時,咱們會從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寬度就變小了。繪製完以後調回去便可。

相關文章
相關標籤/搜索