Unity物品生成系统:脚本实例化后保留ScriptableObject编辑能力
嘿,这个问题我之前做物品系统的时候也碰到过!你的装饰器+蓝图+Builder思路其实挺靠谱的,Inspector只显示最后一个装饰器属性的核心问题是Unity的序列化机制——它只会默认识别最外层的对象实例,之前叠加的装饰器属性被“藏”起来了。给你几个可行的解决方案,按推荐程度排序:
1. 改用复合组件模式(最推荐)
放弃装饰器的层层包装,把不同物品类型的属性拆成独立的可序列化类,直接在ItemBlueprint里存放这些组件的实例(允许为空)。这样Unity Inspector能直接显示所有启用的属性,完全不会有覆盖问题。
示例代码:
// 定义各类型的属性类 [Serializable] public class ConsumableProperties { public string description; public int healAmount; } [Serializable] public class EquipmentProperties { public EquipmentSlot equipmentSlot; // 假设你有这个枚举 public int attackBonus; } // 物品蓝图类 public class ItemBlueprint : ScriptableObject { public string itemName; public Sprite icon; // 可空的属性组件,为空则表示物品不具备该类型能力 public ConsumableProperties consumableProps; public EquipmentProperties equipmentProps; } // ItemBuilder逻辑调整 public static class ItemBuilder { public static Item Build(ItemBlueprint blueprint) { var item = new Item(blueprint.itemName, blueprint.icon); // 添加消耗品能力 if (blueprint.consumableProps != null) { item.AddComponent(new ConsumableComponent(blueprint.consumableProps.description, blueprint.consumableProps.healAmount)); } // 添加装备能力 if (blueprint.equipmentProps != null) { item.AddComponent(new EquipmentComponent(blueprint.equipmentProps.equipmentSlot, blueprint.equipmentProps.attackBonus)); } return item; } }
这种方式的优势是直观、易扩展——后续要加新的物品类型(比如任务物品、可投掷物品),只需要新增对应的[Serializable]属性类就行,完全不用改现有逻辑,Inspector里的编辑体验也和原生字段一致。
2. 用[SerializeReference]改进装饰器序列化
如果你坚持想用装饰器模式,可以借助Unity 2020.1及以上版本支持的[SerializeReference]特性,让Unity序列化并显示所有层级的装饰器属性。
示例代码:
// 基类调整 public abstract class Item { public string Name { get; protected set; } } public abstract class ItemDecorator : Item { [SerializeReference] protected Item wrappedItem; protected ItemDecorator(Item item) { wrappedItem = item; Name = item.Name; } } // 具体装饰器 [Serializable] public class ConsumableDecorator : ItemDecorator { public string description; public int healAmount; public ConsumableDecorator(Item item) : base(item) { } } [Serializable] public class EquipmentDecorator : ItemDecorator { public EquipmentSlot equipmentSlot; public int attackBonus; public EquipmentDecorator(Item item) : base(item) { } } // 蓝图类调整 public class ItemBlueprint : ScriptableObject { public string baseItemName; // 直接存装饰器实例,用SerializeReference序列化 [SerializeReference] public List<ItemDecorator> decorators; }
使用[SerializeReference]后,Unity Inspector会显示每个装饰器的具体类型和对应的属性,你可以在蓝图里按顺序添加装饰器,编辑器会逐一展示它们的字段。不过要注意,这个特性对序列化的类有一些要求(比如必须有[Serializable]标记,不能是泛型类等),而且在Inspector里添加装饰器时需要手动选择类型。
3. 自定义Inspector编辑器
如果不想改现有代码结构,就写一个自定义Editor类,手动遍历所有装饰器并绘制它们的属性。这种方式需要你熟悉Unity的Editor API,但能完全自定义显示效果。
示例代码(简化版):
using UnityEditor; [CustomEditor(typeof(ItemBlueprint))] public class ItemBlueprintEditor : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); var blueprint = target as ItemBlueprint; if (blueprint == null) return; // 假设你的蓝图里有一个获取所有已应用装饰器的方法 var allDecorators = blueprint.GetAllAppliedDecorators(); EditorGUILayout.Space(); EditorGUILayout.LabelField("All Decorator Properties", EditorStyles.boldLabel); foreach (var decorator in allDecorators) { EditorGUILayout.Space(); EditorGUILayout.LabelField(decorator.GetType().Name, EditorStyles.label); // 用反射获取装饰器的所有可序列化字段并绘制 var fields = decorator.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); foreach (var field in fields) { var value = field.GetValue(decorator); // 针对不同字段类型做适配,这里简化处理 if (field.FieldType == typeof(string)) { var newValue = EditorGUILayout.TextField(field.Name, (string)value); field.SetValue(decorator, newValue); } else if (field.FieldType.IsEnum) { var newValue = EditorGUILayout.EnumPopup(field.Name, (Enum)value); field.SetValue(decorator, newValue); } else if (field.FieldType == typeof(int)) { var newValue = EditorGUILayout.IntField(field.Name, (int)value); field.SetValue(decorator, newValue); } } } // 保存修改 if (GUI.changed) { EditorUtility.SetDirty(blueprint); } } }
这个方法的好处是不用动现有运行时代码,但缺点是需要维护Editor代码,而且反射绘制字段的方式可能不如原生序列化稳定,复杂类型(比如Sprite、自定义类)需要额外适配。
总的来说,最推荐第一种复合组件的方式,它最符合Unity的序列化习惯,后续维护和扩展成本最低。
内容的提问来源于stack exchange,提问作者Sorade




