You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity自定义Inspector:为列表添加序列化类并显示子类属性

解决Unity多态Scriptable Object列表的Inspector属性编辑问题

1. 先搞定子类属性的序列化问题

Unity默认序列化对多态类型支持有限,直接用List<BehaviourBase>只会保存基类信息,子类属性不会被正确序列化。核心解决办法是用SerializeReference特性(Unity 2019.3+支持),它能完整序列化多态类型的所有字段。

修改你的ObjectData脚本:

using UnityEngine;
using System.Collections.Generic;

[CreateAssetMenu(fileName = "ObjectData", menuName = "ScriptableObjects/ObjectData")]
public class ObjectData : ScriptableObject
{
    // 用SerializeReference替代默认序列化,支持多态类型的完整字段保存
    [SerializeReference]
    public List<BehaviourBase> behaviours = new List<BehaviourBase>();
}

同时确保所有BehaviourBase的子类都标记[Serializable]:

using UnityEngine;

[Serializable]
public abstract class BehaviourBase : ScriptableObject
{
    public abstract void Execute();
}

[Serializable]
public class MoveBehaviour : BehaviourBase
{
    public float moveSpeed = 5f;
    public Vector3 targetPosition;

    public override void Execute()
    {
        // 实现逻辑
    }
}

[Serializable]
public class AttackBehaviour : BehaviourBase
{
    public int damage = 10;
    public float attackRange = 3f;

    public override void Execute()
    {
        // 实现逻辑
    }
}

2. 编写自定义Editor脚本绘制子类属性

自定义Editor时不能直接用默认的PropertyField处理整个列表,需要遍历每个元素,针对子类实例单独绘制其属性:

using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;

[CustomEditor(typeof(ObjectData))]
public class ObjectDataEditor : Editor
{
    private ObjectData _data;

    private void OnEnable()
    {
        _data = target as ObjectData;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        // 添加Behaviour的按钮
        if (GUILayout.Button("Add Behaviour"))
        {
            GenericMenu menu = new GenericMenu();
            // 获取所有BehaviourBase的非抽象子类
            var behaviourTypes = System.AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(asm => asm.GetTypes())
                .Where(t => t.IsSubclassOf(typeof(BehaviourBase)) && !t.IsAbstract);

            foreach (var type in behaviourTypes)
            {
                menu.AddItem(new GUIContent(type.Name), false, () =>
                {
                    Undo.RecordObject(_data, "Add Behaviour");
                    var newBehaviour = ScriptableObject.CreateInstance(type) as BehaviourBase;
                    _data.behaviours.Add(newBehaviour);
                    EditorUtility.SetDirty(_data);
                });
            }
            menu.ShowAsContext();
        }

        // 绘制每个Behaviour的属性
        EditorGUILayout.Space();
        EditorGUILayout.LabelField("Behaviours", EditorStyles.boldLabel);

        for (int i = 0; i < _data.behaviours.Count; i++)
        {
            var behaviour = _data.behaviours[i];
            if (behaviour == null)
            {
                _data.behaviours.RemoveAt(i);
                i--;
                continue;
            }

            // 折叠框显示类名,展开后展示属性
            using (var foldout = new EditorGUILayout.FoldoutScope(true, behaviour.GetType().Name))
            {
                if (foldout.value)
                {
                    EditorGUI.indentLevel++;
                    // 序列化并绘制子类的所有可编辑属性
                    SerializedObject serializedBehaviour = new SerializedObject(behaviour);
                    serializedBehaviour.Update();

                    SerializedProperty prop = serializedBehaviour.GetIterator();
                    // 跳过m_Script字段,避免误改脚本引用
                    if (prop.NextVisible(true))
                    {
                        do
                        {
                            if (prop.name != "m_Script")
                            {
                                EditorGUILayout.PropertyField(prop, true);
                            }
                        } while (prop.NextVisible(false));
                    }

                    serializedBehaviour.ApplyModifiedProperties();
                    EditorGUI.indentLevel--;
                }
            }

            // 删除按钮
            if (GUILayout.Button($"Remove {behaviour.GetType().Name}", GUILayout.Width(120)))
            {
                Undo.RecordObject(_data, "Remove Behaviour");
                _data.behaviours.RemoveAt(i);
                i--;
                EditorUtility.SetDirty(_data);
            }

            EditorGUILayout.Space();
        }
    }
}

3. 为什么之前的Property Drawer没生效?

大概率是这两个原因:

  • 没给列表字段加[SerializeReference],导致子类属性根本没被序列化,Property Drawer找不到可绘制的字段;
  • Property Drawer是针对单个固定类型字段的,而你需要处理的是列表中的多态元素,这种场景下自定义Editor脚本比Property Drawer更适配。

注意事项

  • SerializeReference要求Unity版本在2019.3及以上;
  • 如果子类是普通可序列化类(不继承ScriptableObject),直接标记[Serializable]即可,创建实例用Activator.CreateInstance(type);
  • 用Undo.RecordObject和EditorUtility.SetDirty确保操作可撤销,且修改能正确保存到资产中。

内容的提问来源于stack exchange,提问作者Chen Robin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:43:20