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 - 所有枚举值名称的字符串数组
* 返回值:用户新选择的索引
*/
}
}
运行效果图