Unity - 在Inspector中編輯自訂類別作為型別的參數,並簡單使用CustomPropertyDrawer美化GUI顯示

目的

  主要的目標在於讓使用自訂類別作為型別宣告的物件,可以在Unity的Inspector視窗當中也出現編輯框,讓使用者可以直接編輯該物件。同時使用CustomPropertyDrawer來美化介面,讓編輯更可以符合自己的需求。



  這個跟之前這篇很類似(Unity - 如何在Script中使用自己製作的PropertyAttribute)。

  不過不同的地方在於,之前那篇是建立一個PropertyAttribute並且給一個變數加上這個標籤,事實上就算不加這個標籤,原本的那個變數還是會出現在InspectorWindow中可以編輯,因為參數原本的型別就是Unity可以辨識的型別(int、float、Vector等等),使用PropertyAttribute就是當需要給這些參數做某些限制,或是單獨改變該參數的GUI讓編輯某些數值方便一點。

  我覺得在顯示上的差異大概就是,如果我只需要某個宣告的參數在Unity Inspector視窗的顯示上要稍微做些改變,就使用PropertyAttribute給這個參數加上標籤;而如果我需要一整個類別內的物件在Inspector視窗中都改變顯示,就使用PropertyDrawer,就如同字面一次改一整個抽屜裡的東西。



說明

  例如:一個單純的自訂類別,裡頭裝了幾個參數,這個類別我可能是作為一個容器,裡面放幾個相關的物件。

public class MyClass {
    public int value1;
    public int value2;
    public MyEnum myEnum; 
}

public MyEnum { test1, test2 }



  之後在製作Component的時候需要用到這個類別,直接使用這個類別作為型別宣告field後,在Unity當中的顯示可能會不如預期。
public class MyComponent : MonoBehaviour {
    public int myValue1; //在Inspector Window可以編輯
    public int myValue2; //在Inspector Window可以編輯
    public MyClass myClassValue; //在Inspector Window不會出現編輯區塊
}



  結果只有兩個Int型別的物件出現在Unity的Inspector視窗當中,而自訂的類別就被忽略了,所以在這個情況下要讓自訂的這個類別出現在編輯視窗中,首先需要給這個類別加個標籤,讓Unity可以把裡面的數值序列化保留下來。
[System.Serializable]
public class MyClass {
    public int value1;
    public int value2;
    MyEnum myEnum;  
}

[System.Serializable] 
public MyEnum { test1, test2 } 


  基本上只要加上Serializable這個標籤大部分的field都會出現在Unity編輯室窗中可以編輯,這樣子自己製作的類別就不用一個人在旁邊玩沙了。





美化介面

  接著就是使用CustomPropertyDrawer標籤來美化在Inspector中顯示的樣子,同時製作符合自己需求的PropertyDrawer GUI。


  首先製作一個新的類別繼承PropertyDrawer,並且加上CustomPropertyDrawer標籤,目標的類別就是自訂的類別。
[CustomPropertyDrawer(typeof(MyClass))]
public class MyClassDrawer : PropertyDrawer {
    public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
        return base.GetPropertyHeight (property, label) + 36f; //單行高我設定18f,原始單行再加上兩行的高度,這邊就看自己需求調整
    }

    //這邊調整顯示的GUI
    public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
        position.height = 18f; //設定單行高度18

        //使用property.FindPropertyRelative("myEnum")來取得我的類別中的field
        EditorGUI.PropertyField(position, property.FindPropertyRelative("myEnum"), new GUIContent("Enum"));

        position.y += position.height; //往下移一行,避免物件重疊
        //這次使用Slider顯示拉桿來調整int數值
        EditorGUI.IntSlider(position, property.FindPropertyRelative("value1"), 0, 100, "V1"); //設定value1欄位

        position.y += position.height; //再往下移一行
        EditorGUI.IntSlider(position, property.FindPropertyRelative("value2"), 0, 100, "V2"); //設定value2欄位
    }
}


  再回到Unity中就可以看到改變了,這邊可以看到宣告單獨一個跟宣告陣列顯示的狀態,我MyClass裡面的值都有適當的GUI配置了。


  不過這在顯示上還是有些不方便,就是當數值很多的時候,整個Component的長度會被拉大,所以這邊需要一個Fold可以把數值折疊起來,同時還可以把數值分類摺疊。


  因為要摺疊所以Property的高就會跟著改變,首先調整GetPropertyHeight的方法,使用property參數裡面的isExpanded來檢查是否展開
public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
        return property.isExpanded ? 72f : 18f; //單行18f,因為折疊打開後有標籤跟3行property,所以打開後的高要設定18*4=72f
}


  接著修改OnGUI的顯示,同樣要檢查是否展開,在設定摺疊標籤的位置的時候要注意height的設定,下圖來看紅色框是我設定foldoutPosition.height是18f而藍色框則是36f,如果是36f會出現一個狀況就是,我要點Enum按鈕,但是點下去同時也點到Fold所以同時也關閉摺疊,因此這邊要看需求跟介面配置調整。


public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
    position.height = 18f; //單行高度

    //設定一個新的折疊標籤,不使用原本類別的名稱
    label = EditorGUI.BeginProperty(position, new GUIContent("My Title"), property);

    Rect foldoutPosition = position;
    foldoutPosition.height = 18f; //看需求調整
    property.isExpanded = EditorGUI.Foldout(foldoutPosition, property.isExpanded, label, true); //使用Foldout來開關折疊

    //如果Property沒有展開就不用顯示下面的欄位了
    if (property.isExpanded)
    {
        //這邊就原本的顯示
        position.y += position.height;
        EditorGUI.PropertyField(position, property.FindPropertyRelative("myEnum"), new GUIContent("Enum"));

        position.y += position.height;
        EditorGUI.IntSlider(position, property.FindPropertyRelative("value1"), 0, 100, "V1"); //設定value1欄位

        position.y += position.height;
        EditorGUI.IntSlider(position, property.FindPropertyRelative("value2"), 0, 100, "V2"); //設定value2欄位
    }

    EditorGUI.EndProperty();
}


  到這邊就可以看到摺疊標籤的名稱不是原本的類別名稱,以及單獨宣告跟陣列宣告的物件都可以摺疊起來了。


  最後稍微調整一下縮排,可以讓顯示上更可以明顯區分那些數值是在摺疊裡面,這邊只要在property.isExpanded裡面做些調整即可,同時GetPropertyHeight()裡面回傳的值改為return property.isExpanded ? 54f : 18f;,變成三行的高原因是因為我讓兩個參數擺在同一行。

if (property.isExpanded)
{
    //調整成縮排後的Rect數值
    position = EditorGUI.IndentedRect(position);
    position.width /= 2f; //因這邊我想單行要放兩個欄位,所以把寬分一半

    //紀錄當前的縮排數值,等結束的時候再設定回去,避免影響到後面的物件
    int oldIndentLevel = EditorGUI.indentLevel;
    EditorGUI.indentLevel = 1; //設定縮排

    //調整欄位label的寬度,避免label跟input之間太多空白,看自己需求或label長度自行調整
    EditorGUIUtility.labelWidth = 50f;

    position.y += position.height; //往下移一行
    EditorGUI.PropertyField(position, property.FindPropertyRelative("myEnum"), new GUIContent("Enum"));

    position.y += position.height; //往下移一行
    EditorGUI.IntSlider(position, property.FindPropertyRelative("value1"), 0, 100, "V1"); //設定value1欄位

    position.x += position.width; //往右移一半寬
    EditorGUI.IntSlider(position, property.FindPropertyRelative("value2"), 0, 100, "V2"); //設定value2欄位

    EditorGUI.indentLevel = oldIndentLevel;
}


  可以看到Enum跟V1開頭位置不再跟My Title平行了,看起來就更容易分辨哪些物件是屬於這個摺疊的了。

  調整GUI上還有許多方式讓顯示更美觀,這邊就簡單的使用。



結語

  到這邊就結束了,已經把自訂類別的PropertyDrawer簡單製作完,之後使用的時候直接用自訂的類別作為型別宣告即可,之後就會在編輯視窗中可以編輯類別中的各個參數

public class Test : MonoBehaviour {
    public List<Vector2> vectors; //這個Unity已經會自動顯示在InspectorWindow理了
    public MyClass myClass; //宣告單個自訂Class型別物件
    public MyClass[] myClasses; //宣告陣列自訂Class
}

1 comment:

Unknown said...

謝謝大大!找此方法很久了!

Post a Comment