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

如何在Unity Inspector中替换列表内的QuestStep子类元素?

Unity任务系统Inspector自定义绘制问题

问题描述

我通过ScriptableObject实现游戏任务系统,Quest类包含一个QuestStep类型的列表。其中QuestStep是抽象类,KillStep和ReachDestinationStep继承自它。需要在Inspector窗口实现添加步骤并选择类型的功能,UI已完成设计:

  • 列表中的KillStep元素为图1样式
  • 抽象QuestStep采用带下拉框和按钮的样式(图2、3),支持选择类型并替换选中元素

当前已接近实现,但只能修改整个列表,无法单独获取列表元素的引用或索引。尝试使用objectReferenceValue时报错:type is not a supported pptr value,且managedReferenceValue为空。

现有代码示例

[CustomPropertyDrawer(typeof(QuestStep))]
public class QuestStepDrawer : PropertyDrawer
{
    private SerializedProperty currentProperty;
    enum QuestStepType
    {
        Kill,
        ReachDestination
    }
    private QuestStepType stepType = QuestStepType.Kill;
    

    public override VisualElement CreatePropertyGUI(SerializedProperty property)
    {
        currentProperty = property;
        var container = new VisualElement();
        container.Add(new EnumField("Step to create:", stepType));
        container.Add(new Button(InitStep));

        return container;
    }

    private void InitStep()
    {
        //here i want something like SetValue(currentProperty.objectReference = new KillStep());
        fieldInfo.SetValue(currentProperty.serializedObject.targetObject, new List<QuestStep>(){new KillStep()});
    }
}

解决方案

核心问题是混淆了Unity序列化中**对象引用(objectReferenceValue)和托管引用(managedReferenceValue)**的区别:QuestStep是自定义C#类(非Unity内置Object子类),属于托管引用,需用managedReferenceValue处理。

修改后的代码实现

using UnityEngine;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

[CustomPropertyDrawer(typeof(QuestStep))]
public class QuestStepDrawer : PropertyDrawer
{
    private SerializedProperty currentProperty;
    private EnumField typeSelector;

    enum QuestStepType
    {
        Kill,
        ReachDestination
    }

    public override VisualElement CreatePropertyGUI(SerializedProperty property)
    {
        currentProperty = property;
        var container = new VisualElement();

        // 根据已有实例初始化类型选择器
        var existingStep = property.managedReferenceValue as QuestStep;
        var defaultType = QuestStepType.Kill;
        if (existingStep is ReachDestinationStep)
            defaultType = QuestStepType.ReachDestination;

        typeSelector = new EnumField("步骤类型:", defaultType);
        container.Add(typeSelector);

        var replaceBtn = new Button(ReplaceStep) { text = "创建/替换步骤" };
        container.Add(replaceBtn);

        // 若已有步骤实例,显示其属性面板
        if (existingStep != null)
        {
            var detailPanel = new PropertyField(property, "步骤详情");
            container.Add(detailPanel);
        }

        return container;
    }

    private void ReplaceStep()
    {
        if (currentProperty == null) return;

        QuestStep newStep = null;
        var selectedType = (QuestStepType)typeSelector.value;

        switch (selectedType)
        {
            case QuestStepType.Kill:
                newStep = new KillStep();
                break;
            case QuestStepType.ReachDestination:
                newStep = new ReachDestinationStep();
                break;
        }

        if (newStep != null)
        {
            // 给当前列表元素赋值托管引用
            currentProperty.managedReferenceValue = newStep;
            // 应用修改并刷新Inspector
            currentProperty.serializedObject.ApplyModifiedProperties();
            EditorUtility.SetDirty(currentProperty.serializedObject.targetObject);
        }
    }
}

关键注意事项

  1. 序列化标记:确保QuestStep及其子类都添加[System.Serializable]特性,否则Unity无法序列化这些类。
  2. 列表序列化:Quest类中的List<QuestStep>字段需添加[SerializeField],才能在Inspector中显示并编辑。
  3. 获取列表索引:若需获取当前元素在列表中的位置,可解析property.propertyPath:
    var pathSegments = currentProperty.propertyPath.Split('[', ']');
    if (int.TryParse(pathSegments[1], out int elementIndex))
    {
        // elementIndex即为当前元素在列表中的索引
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:35:50