如何在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); } } }
关键注意事项
- 序列化标记:确保
QuestStep及其子类都添加[System.Serializable]特性,否则Unity无法序列化这些类。 - 列表序列化:Quest类中的
List<QuestStep>字段需添加[SerializeField],才能在Inspector中显示并编辑。 - 获取列表索引:若需获取当前元素在列表中的位置,可解析
property.propertyPath:var pathSegments = currentProperty.propertyPath.Split('[', ']'); if (int.TryParse(pathSegments[1], out int elementIndex)) { // elementIndex即为当前元素在列表中的索引 }
内容的提问来源于stack exchange,提问作者votizlov
相关产品推荐
相关产品推荐

