第三章自定义检视面板_创建自定义编辑器类_实现自定义检视面板中的GUI内容(本章进度(1/9))
3.1创建自定义编辑器类
3.1.1 如何实现自定义检视面板中的GUI内容
Editor类中的虚方法OnInspectorGUI()定义了组件的检视面板如何绘制,重写该方法可实现自定义,想要拓展的话,则要保留base.OnInspectorGUI。它不止可以用于自定义的组件,同时unity自带的组件也可以,比如Tranform之类的。
我们已经有了Mono类,为什么还要实现OnInspector(),我们如果自定义编辑器类操作字段,需要四步
- 1.需要先获取到指定类的指定字段,
- 2.显示到inpector面板
- 3.将面板上修改的值,赋值给指定字段
- 4.数据需要调用api实现修改数据保存。
一下多了四步,而如果使用Mono,我们不需要做这些四步,我们为什么要费劲做四步呐,他的好处是什么,初学的时候,也困惑我一阵,但其实看似多走了很多步,但是长远来看,拓展出的工具却是节省了更多时间,下面是一些区别。
特性 | OnInspectorGUI() 自定义 | 默认 MonoBehaviour 字段 |
---|---|---|
布局控制 | 完全自由定制 | 固定顺序排列 |
功能扩展 | 可添加按钮、滑块、预览区等复杂控件 | 仅显示基础字段 |
撤销支持 | 完整支持 (Ctrl+Z 回退所有操作) | 部分支持 (仅序列化字段) |
多对象编辑 | 同时批量修改多个选中对象 | 有限支持 (仅公共字段) |
安全封装 | 保持私有字段不可见,不破坏封装性 | 需要暴露字段才能显示 |
内置组件扩展 | 可为Transform等系统组件添加功能 | 无法扩展系统组件 |
开发效率 | 需额外编写编辑器代码 | 零配置自动显示 |
以下是举个显示在自定义组件字段的例子
1.首先定义这个自定义组件
代码如下
using UnityEngine;public class CustomComponent : MonoBehaviour
{//在 Unity 编辑器扩展中,访问目标组件字段的途径有两种//1.通过序列化系统:可访问标记为 public(Unity会自动序列化public字段) 或添加了 [SerializeField] 特性的字段//2.通过反射:可访问任意访问修饰符的字段(包括 private/protected),但需手动处理数据持久化//他们的具体区别看下表吧,一般来说都是通过第一种, 第二种很少用。public int intValue;[SerializeField] private string stringValue;[SerializeField] private bool boolValue;[SerializeField] private GameObject go;public enum ExampleEnum{Option1,Option2,Option3}[SerializeField] private ExampleEnum exampleEnum;
}
特性 | 序列化系统 | 反射 |
---|---|---|
字段可见性 | public / [SerializeField] | 任意(含private) |
撤销支持 | ✅ 自动 | ❌ 需手动实现 |
多对象编辑 | ✅ 自动同步 | ❌ 需遍历targets |
数据持久化 | ✅ 自动标记脏数据 | ❌ 需调用SetDirty() |
重命名安全 | ✅ 通过属性名查找 | ❌ 硬编码字符串易断裂 |
性能 | ⭐⭐⭐⭐ 高效 | ⭐ 反射开销较大 |
2.显示在InSpector面板以及将面板上修改的值,赋值给指定字段
在这里我们一下完成了第二步和第三步,我们需要使用一个特性CustomEditor用来作用于组件。这里需要理解一下SeriailizedProperty这个类,他是我们用来操作数据的中介类,通过他来获取,修改。
SerializedProperty点击跳转
using System.Reflection; // 引入反射命名空间,用于访问私有字段
using UnityEditor;
using UnityEngine; /// <summary>
/// 使用CustomEditor特性,指定这个编辑器类是为CustomComponent组件服务的
/// </summary>
[CustomEditor(typeof(CustomComponent))]
public class CustomCompoentEditor : Editor
{private CustomComponent component; // 当前正在编辑的CustomComponent组件实例// 序列化属性,用于访问和修改组件中的序列化字段private SerializedProperty stringValueProperty; // 字符串类型字段private SerializedProperty gameObjectProperty; // GameObject类型字段private SerializedProperty enumValue; // 枚举类型字段// 反射字段信息,用于访问私有字段private FieldInfo boolValueFieldInfo; // 布尔类型私有字段// 当编辑器启用时调用,用于初始化private void OnEnable(){/*这里只是为了演示不同的获取方式 一种当字段是public时可以直接访问,或者被[serilaredField]*///工作原理就是从target中获取到当前正在编辑的组件实例,然后去查找指定字段// 获取当前正在编辑的组件实例component = (CustomComponent)target;// 通过序列化对象查找并获取各个序列化属性stringValueProperty = serializedObject.FindProperty("stringValue"); // 查找字符串字段,这里不是反射,是unituy序列化系统gameObjectProperty = serializedObject.FindProperty("go"); // 查找GameObject字段enumValue = serializedObject.FindProperty("exampleEnum"); // 查找枚举字段// 初始化反射字段,这里用到了反射,只是为了演示所以用到反射boolValueFieldInfo = component.GetType().GetField("boolValue",BindingFlags.NonPublic | BindingFlags.Instance);}/// <summary>/// 重写OnInspectorGUI方法,自定义Inspector界面/// </summary>public override void OnInspectorGUI(){CustomExample(); // 调用自定义的绘制方法}/// <summary>/// 自定义绘制Inspector界面的方法/// </summary>private void CustomExample(){// 绘制公共整数字段component.intValue = EditorGUILayout.IntField("Int Value", component.intValue);/** EditorGUILayout.IntField:* 创建一个整数输入字段* 参数1:"Int Value" - 显示在字段前的标签* 参数2:component.intValue - 当前整数值* 返回值:用户输入的新整数值*/// 绘制字符串输入字段stringValueProperty.stringValue = EditorGUILayout.TextField("String Value",stringValueProperty.stringValue);/** EditorGUILayout.TextField:* 创建一个文本输入框* 参数1:"String Value" - 显示在输入框前的标签* 参数2:stringValueProperty.stringValue - 当前字符串值* 返回值:用户输入的新字符串*/// 绘制私有布尔字段(通过反射访问)boolValueFieldInfo.SetValue(component,EditorGUILayout.Toggle("Bool Value",(bool)boolValueFieldInfo.GetValue(component)));/** EditorGUILayout.Toggle:* 创建一个布尔值开关(复选框)* 参数1:"Bool Value" - 显示在开关前的标签* 参数2:(bool)boolValueFieldInfo.GetValue(component) - 当前布尔值* 返回值:用户设置的新布尔值* * boolValueFieldInfo.SetValue:* 通过反射将新值设置回组件的私有字段*/// 绘制枚举下拉选择框enumValue.enumValueIndex = EditorGUILayout.Popup("Enum Value",enumValue.enumValueIndex,enumValue.enumNames);/** EditorGUILayout.Popup:* 创建一个下拉选择框* 参数1:"Enum Value" - 显示在下拉框前的标签* 参数2:enumValue.enumValueIndex - 当前选中的枚举值索引* 参数3:enumValue.enumNames - 所有枚举值名称的字符串数组* 返回值:用户新选择的索引*/}
}
运行效果图