[CustomEditor(typeof(XXXConfig))]
public class XXXConfigEditor : UnityEditor.Editor
这是一个 属性标签(Attribute),告诉 Unity:
"当用户在 Inspector 中选中一个
XXXConfig类型的 ScriptableObject 资产时,不要用默认的 Inspector,而是用我写的XXXConfigEditor来渲染面板。"
用户在 Hierarchy/Project 中选中 XXXConfig 资产
↓
Unity 检测到 [CustomEditor(typeof(XXXConfig))]
↓
实例化 XXXConfigEditor
↓
调用 OnInspectorGUI() 方法(你必须重写这个方法)
↓
你在 OnInspectorGUI() 里用 GUILayout/EditorGUILayout 绘制自定义控件
↓
用户点击按钮、修改字段 → 通过 serializedObject.ApplyModifiedProperties() 保存修改
serializedObject 是继承自 UnityEditor.Editor 基类的内置属性
public class XXXConfigEditor : UnityEditor.Editor // 继承了 Editor
{
// serializedObject 来自父类 UnityEditor.Editor
// 你不需要声明,直接用就行
}
serializedObject 是 SerializedObject 类型 ,它是你正在编辑的目标资产(即 TutorialConfig 实例)的序列化包装器。
核心作用
| 作用 | 说明 |
|---|---|
| 读取字段 | 通过 serializedObject.FindProperty("fieldName") 访问资产里的字段 |
| 修改字段 | 通过 property.intValue = xxx 修改值 |
| 保存修改 | 调用 serializedObject.ApplyModifiedProperties() 把修改写入磁盘 |
| 撤销支持 | 配合 Undo.RecordObject() 实现 Ctrl+Z 撤销 |
| 多对象编辑 | 同时选中多个资产时,自动处理共有的可编辑字段 |
public override void OnInspectorGUI()
{
// 1. 更新序列化对象(从磁盘读取最新状态)
serializedObject.Update();
// 2. 获取字段
SerializedProperty xxList = serializedObject.FindProperty("lists");
// 3. 绘制字段(Unity 自动处理 UI)
EditorGUILayout.PropertyField(xxList);
// 4. 应用修改(保存回资产文件)
serializedObject.ApplyModifiedProperties();
}
ReorderableList 是 Unity 编辑器内置的可拖拽排序列表控件,专门用于在 Inspector 中展示和操作数组/列表。
using UnityEditorInternal; // 命名空间
ReorderableList list = new ReorderableList(serializedObject, property);
它提供了一个可视化的列表 UI,支持:
| 功能 | 说明 |
|---|---|
| 拖拽排序 | 鼠标拖拽元素上下移动 |
| 添加/删除 | 右侧有 + - 按钮 |
| 折叠/展开 | 点击左侧箭头展开子字段 |
| 多选 | 支持框选多个元素批量操作 |
| 自定义绘制 | 可以重写回调方法自定义每个元素的显示 |
var dropdown = new StageTypeDropdown(_dropdownState, onSelected);
dropdown.Show(buttonRect);
当执行 dropdown.Show(buttonRect) 时,Unity 内部的 AdvancedDropdown 会:
弹出下拉框窗口
立即调用 BuildRoot() 构建下拉框的内容树
渲染树节点显示给用户
用户点击 ReorderableList 的 + 按钮
↓
onAddDropdownCallback 触发
↓
StageTypeSelector.Show(buttonRect, OnStageTypeSelected)
↓
new StageTypeDropdown(...) // 创建实例
↓
dropdown.Show(buttonRect) // 显示下拉框
↓
[Unity 内部] AdvancedDropdown.Show()
↓
[Unity 内部] 调用 BuildRoot() ← 你的重写方法在这里被调用
↓
返回 AdvancedDropdownItem 根节点
↓
渲染下拉框内容
[SerializeReference]
[Serializable]
public abstract class BaseType
{
[SerializeField] private string _typeName= "xxx";
}
// ❌ 默认序列化:只保存基类字段,丢失子类信息
public List<BaseType> BaseTypes;
// ✅ 使用 SerializeReference:保存实际类型 + 子类字段
[SerializeReference]
public List<BaseType> BaseTypes;
| 默认序列化 | [SerializeReference] |
|
|---|---|---|
| 序列化时 | 只保存基类 Stage 的字段 |
保存实际类型 + 所有字段 |
| 反序列化后 | 变成 Stage(基类) |
还原为 FocusSampleStage(子类) |
| 子类字段 | 丢失 | 完整保留 |
| null 值 | 不允许(总是创建实例) | 允许 null |
| 多态引用 | 不支持 | 支持 |
// ✅ 接口
[SerializeReference] public ITrigger Trigger;
// ✅ 抽象类
[SerializeReference] public Stage CurrentStage;
// ✅ 数组/列表
[SerializeReference] public List<Stage> Stages;
// ❌ 不能用在值类型(struct、int、Color 等)
[SerializeReference] public int Value; // 编译报错
// ❌ 不能用在继承自 MonoBehaviour/ScriptableObject 的类
[SerializeReference] public MyMonoBehaviour Ref; // 运行时报错
// ❌ 不能用在 GameObject/Transform 等 Unity 原生对象
[SerializeReference] public GameObject Go; // 不生效
子类必须标注 [Serializable]
[Serializable] // ← 必须!
public class TestTypes : BaseType
{
[SerializeField] private float _animationDuration;
}
字段本身不需要再加 [SerializeField]
// [SerializeReference] 本身就表示"参与序列化",不需要 [SerializeField]
[SerializeReference] public List<BaseType> baseTypes;
// 但子类内部的字段仍然需要 [SerializeField]
[Serializable]
public class TestTypes: BaseType
{
[SerializeField] private float _duration; // ← 需要
}