1. 繼承Editor,重寫OnInspectorGUI方法
需求
將TestClass中intData屬性和stringData按指定格式顯示。
實現
定義一個測試類TestClass,一個可序列化類DataClass
[CreateAssetMenu] public class TestClass : ScriptableObject { [Range(0, 10)] public int intData; public string stringData; public List<DataClass> dataList; } [System.Serializable] public class DataClass { [Range(0, 100)] public int id; public Vector3 position; public List<int> list; }
//指定類型 [CustomEditor(typeof(TestClass))] public class TestClassEditor : Editor { SerializedProperty intField; SerializedProperty stringField; void OnEnable() { intField = serializedObject.FindProperty("intData"); stringField = serializedObject.FindProperty("stringData"); } public override void OnInspectorGUI() { // Update the serializedProperty - always do this in the beginning of OnInspectorGUI. serializedObject.Update(); EditorGUILayout.IntSlider(intField, 0, 100, new GUIContent("initData")); EditorGUILayout.BeginHorizontal(); EditorGUILayout.PropertyField(stringField); if(GUILayout.Button("Select")) { stringField.stringValue = EditorUtility.OpenFilePanel("", Application.dataPath, ""); } EditorGUILayout.EndHorizontal(); // Apply changes to the serializedProperty - always do this in the end of OnInspectorGUI. //需要在OnInspectorGUI之前修改屬性,否則無法修改值 serializedObject.ApplyModifiedProperties(); base.OnInspectorGUI(); } }
Editor嵌套
通過Edtiro.CreateEditor可實現Editor的嵌套。
創建一個類TestClass2,它包含一個TestClass的屬性。
[CreateAssetMenu] public class TestClass2 : ScriptableObject { public TestClass data; }
創建一個Test2Class的asset。它的Inspector面板的默認顯示:
它並沒有把TestClass的屬性顯示出來,如果要查看TestClass的屬性,必須雙擊,跳到相應界面,但這樣有看不到TestClass2的屬性。
如果想在Test2Class的Inspector面板中直接看到並且可以修改TestClass的屬性,可以重寫TestClass2的Editor,並在其中嵌套TestClass的Editor。
1 [CustomEditor(typeof(TestClass2))] 2 public class TestClass2Editor : Editor 3 { 4 Editor cacheEditor; 5 public override void OnInspectorGUI() 6 { 7 // Update the serializedProperty - always do this in the beginning of OnInspectorGUI. 8 serializedObject.Update(); 9 //顯示TestClass2的默認UI 10 base.OnInspectorGUI(); 11 GUILayout.Space(20); 12 var data = ( (TestClass2)target ).data; 13 if(data != null) 14 { 15 //創建TestClass的Editor 16 if (cacheEditor == null) 17 cacheEditor = Editor.CreateEditor(data); 18 GUILayout.Label("this is TestClass2"); 19 cacheEditor.OnInspectorGUI(); 20 } 21 } 22 }
這樣就可以直接在TestClass2的面板中直接查看和編輯TestClass的屬性。
2. 使用PropertyDrawer
如果想修改某種特定類型的顯示,使用繼承Editor的方式就會變得很麻煩,因為所有使用特定類型的asset都需要去實現一個自定義的Editor,效率非常低。這種情況就可以通過繼承PropertyDrawer的方式,對指定類型的屬性,進行統一顯示。
需求
為Inspector面板中的所有string屬性添加一個選擇文件按鈕,選中文件的路徑直接賦值給該變量。
實現
1 [CustomPropertyDrawer(typeof(string))] 2 public class StringPropertyDrawer : PropertyDrawer 3 { 4 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 5 { 6 Rect btnRect = new Rect(position); 7 position.width -= 60; 8 btnRect.x += btnRect.width - 60; 9 btnRect.width = 60; 10 EditorGUI.BeginProperty(position, label, property); 11 EditorGUI.PropertyField(position, property, true); 12 if (GUI.Button(btnRect, "select")) 13 { 14 string path = property.stringValue; 15 string selectStr = EditorUtility.OpenFilePanel("選擇文件", path, ""); 16 if (!string.IsNullOrEmpty(selectStr)) 17 { 18 property.stringValue = selectStr; 19 } 20 } 21 EditorGUI.EndProperty(); 22 } 23 }
加了一個PropertyDrawer之后,Inspector面板中的所有string變量都會額外添加一個Select按鈕。
注意事項
- PropertyDrawer只對可序列化的類有效,非可序列化的類沒法在Inspector面板中顯示。
- OnGUI方法里只能使用GUI相關方法,不能使用Layout相關方法。
- PropertyDrawer對應類型的所有屬性的顯示方式都會修改,例如創建一個帶string屬性的MonoBehaviour:
3. 使用PropertyAttribute
如果想要修改部分類的指定類型的屬性的顯示,直接使用PropertyDrawer就無法滿足條件,這時可以結合PropertyAttribute和PropertyAttribute來實現需求。
需求
為部分指定類的int或float屬性的顯示添加滑動條,滑動條的上下限可根據類和屬性自行設置。
實現
1 public class RangeAttribute : PropertyAttribute 2 { 3 public float min; 4 public float max; 5 public RangeAttribute(float min, float max) 6 { 7 this.min = min; 8 this.max = max; 9 } 10 } 11 [CustomPropertyDrawer(typeof(RangeAttribute))] 12 public class RangeDrawer : PropertyDrawer 13 { 14 // Draw the property inside the given rect 15 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 16 { 17 // First get the attribute since it contains the range for the slider 18 RangeAttribute range = attribute as RangeAttribute; 19 // Now draw the property as a Slider or an IntSlider based on whether it's a float or integer. 20 if (property.propertyType == SerializedPropertyType.Float) 21 EditorGUI.Slider(position, property, range.min, range.max, label); 22 else if (property.propertyType == SerializedPropertyType.Integer) 23 EditorGUI.IntSlider(position, property, (int)range.min, (int)range.max, label); 24 else 25 EditorGUI.LabelField(position, label.text, "Use Range with float or int."); 26 } 27 }
修改TestClass和DataClass
1 [CreateAssetMenu] 2 public class TestClass : ScriptableObject 3 { 4 [Range(0, 10)] 5 public int intData; 6 public string stringData; 7 public List<DataClass> dataList; 8 } 9 [System.Serializable] 10 public class DataClass 11 { 12 [Range(0, 100)] 13 public int id; 14 public Vector3 position; 15 public List<int> list; 16 }
其他
- 需要修改顯示的類都需要滿足Unity的序列化規則
- 這幾種顯示方式對Serializable Class都可以使用,並不需要一定是ScriptableObject。只是在編輯器下,ScriptableObject來保存臨時數據比較常用,所以使用ScriptableObject來做例子。