第三章自定义检视面板_创建自定义编辑器类_实现自定义检视面板中的GUI内容(本章进度(1/9))

发布于:2025-07-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

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

运行效果图
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到