Lesson22-23 - Inspector 窗口拓展
Inspector 拓展概述
通过 CustomEditor 特性,可以完全自定义脚本在 Inspector 窗口中的显示方式。这提供了强大的编辑器自定义能力。
基础框架
创建自定义编辑器脚本
命名约定:在目标脚本名后加 Editor,并放在 Editor 文件夹中。
1 2 3 4 5 6 7 8 9
| using UnityEditor; using UnityEngine;
[CustomEditor(typeof(MyScript))] public class MyScriptEditor : Editor { public override void OnInspectorGUI() { EditorGUILayout.LabelField("这是自定义 Inspector"); } }
|
CustomEditor 特性
1 2 3 4 5
| [CustomEditor(typeof(要自定义的脚本类))] [CustomEditor(typeof(MyScript))] public class MyScriptEditor : Editor { }
|
参数 typeof(MyScript) 指定要自定义的目标脚本。
SerializedObject 和 SerializedProperty
概念
- SerializedObject:代表整个脚本对象,用于序列化和反序列化属性
- SerializedProperty:代表脚本中的单个属性,用于获取和修改属性值
初始化
在 OnEnable 中初始化 SerializedProperty:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| [CustomEditor(typeof(MyScript))] public class MyScriptEditor : Editor { private SerializedProperty healthProperty; private SerializedProperty damageProperty; private void OnEnable() { healthProperty = serializedObject.FindProperty("health"); damageProperty = serializedObject.FindProperty("damage"); } public override void OnInspectorGUI() { serializedObject.Update(); serializedObject.ApplyModifiedProperties(); } }
|
基础使用
PropertyField - 自动处理属性显示
1 2 3 4 5 6 7 8 9
| public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(healthProperty, new GUIContent("生命值")); EditorGUILayout.PropertyField(damageProperty, new GUIContent("伤害")); serializedObject.ApplyModifiedProperties(); }
|
PropertyField 会根据属性类型自动选择合适的控件。
获取属性值
1 2 3 4
| int health = healthProperty.intValue; float damage = damageProperty.floatValue; string name = nameProperty.stringValue; Vector3 pos = posProperty.vector3Value;
|
修改属性值
1 2
| healthProperty.intValue = 100; damageProperty.floatValue = 50.5f;
|
更新和应用
1 2 3 4 5 6 7 8
| public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(healthProperty); serializedObject.ApplyModifiedProperties(); }
|
获取目标对象
target 属性
1 2 3 4 5
| public override void OnInspectorGUI() { MyScript myScript = (MyScript)target; EditorGUILayout.LabelField("脚本名:" + myScript.gameObject.name); }
|
target 直接返回绑定的脚本实例。
数组和 List 处理
使用 PropertyField 显示数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [CustomEditor(typeof(MyScript))] public class MyScriptEditor : Editor { private SerializedProperty itemsProperty; private void OnEnable() { itemsProperty = serializedObject.FindProperty("items"); } public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(itemsProperty, true); serializedObject.ApplyModifiedProperties(); } }
|
自定义数组显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public override void OnInspectorGUI() { serializedObject.Update(); int arraySize = itemsProperty.arraySize; int newSize = EditorGUILayout.IntField("大小", arraySize); if (newSize != arraySize) { while (itemsProperty.arraySize < newSize) { itemsProperty.InsertArrayElementAtIndex(itemsProperty.arraySize); } while (itemsProperty.arraySize > newSize) { itemsProperty.DeleteArrayElementAtIndex(itemsProperty.arraySize - 1); } } for (int i = 0; i < itemsProperty.arraySize; i++) { SerializedProperty element = itemsProperty.GetArrayElementAtIndex(i); EditorGUILayout.PropertyField(element, new GUIContent("项目 " + i)); } serializedObject.ApplyModifiedProperties(); }
|
数组 API
arraySize:获取/设置数组大小
InsertArrayElementAtIndex(int):在指定索引插入默认元素
DeleteArrayElementAtIndex(int):删除指定索引的元素
GetArrayElementAtIndex(int):获取指定索引的 SerializedProperty
实战示例
示例 1:自定义角色编辑器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| [CustomEditor(typeof(Character))] public class CharacterEditor : Editor { private SerializedProperty nameProperty; private SerializedProperty levelProperty; private SerializedProperty healthProperty; private void OnEnable() { nameProperty = serializedObject.FindProperty("characterName"); levelProperty = serializedObject.FindProperty("level"); healthProperty = serializedObject.FindProperty("health"); } public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.LabelField("角色编辑器", EditorStyles.boldLabel); EditorGUILayout.Space(5); EditorGUILayout.PropertyField(nameProperty, new GUIContent("角色名")); EditorGUILayout.PropertyField(levelProperty, new GUIContent("等级")); EditorGUILayout.PropertyField(healthProperty, new GUIContent("生命值")); EditorGUILayout.Space(10); if (GUILayout.Button("充满血量")) { healthProperty.intValue = 100; } serializedObject.ApplyModifiedProperties(); } }
|
示例 2:显示只读属性
1 2 3 4 5 6 7 8 9 10 11 12
| public override void OnInspectorGUI() { serializedObject.Update(); GUI.enabled = false; EditorGUILayout.PropertyField(readOnlyProperty); GUI.enabled = true; EditorGUILayout.PropertyField(editableProperty); serializedObject.ApplyModifiedProperties(); }
|
示例 3:条件显示属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(typeProperty); if (typeProperty.stringValue == "Damage") { EditorGUILayout.PropertyField(damageProperty); } else if (typeProperty.stringValue == "Heal") { EditorGUILayout.PropertyField(healProperty); } serializedObject.ApplyModifiedProperties(); }
|
注意事项
- 必须在 Update 和 ApplyModifiedProperties 之间修改属性:这样改动才能正确同步到目标对象。
- 引用比较要用 Equals:比较 SerializedProperty 时用 Equals 而不是 ==。
- 数组操作会改变大小:InsertArrayElementAtIndex 和 DeleteArrayElementAtIndex 都会改变数组大小。
- 避免频繁的 Update 和 Apply:在一个 OnInspectorGUI 中只需调用一次 Update 和 ApplyModifiedProperties。
- 在 OnEnable 中查找属性:这样性能更好,避免每帧都查找一遍。