自定义编辑器中复杂对象字段的预制体修改无法保存
Unity自定义编辑器字典数据无法保存的解决方案
问题根源:
Unity的原生序列化系统不支持Dictionary类型,即便给字典字段加上[SerializeField]也无法完成正确的序列化/反序列化,导致预制体重新加载时字典值丢失。另外List<KeyValuePair<string, float>>也无法正常工作,因为KeyValuePair是值类型,修改其Value时Unity检测不到列表内部的变化,序列化系统不会记录这些修改。
下面是具体的解决步骤:
1. 替换Dictionary为可序列化的自定义键值对列表
创建一个可序列化的自定义键值对类,让Unity能够正确序列化列表数据:
[System.Serializable] public class SerializableFloatKeyValue { public string key; public float value; public SerializableFloatKeyValue(string key, float value) { this.key = key; this.value = value; } }
修改MovementVariableModifierData类,用自定义列表替代Dictionary:
[System.Serializable] public class MovementVariableModifierData { [ItemType] public string modifierItemID = ""; public int modifierItemIdx; // 替换Dictionary为可序列化的自定义列表 public List<SerializableFloatKeyValue> multipliers = new List<SerializableFloatKeyValue>(); public List<SerializableFloatKeyValue> addends = new List<SerializableFloatKeyValue>(); // 运行时快速转成Dictionary的工具方法 public Dictionary<string, float> GetMultipliersDict() { var dict = new Dictionary<string, float>(); foreach (var kv in multipliers) { dict[kv.key] = kv.value; } return dict; } // 编辑器中快速查找/添加键值对的方法 public SerializableFloatKeyValue GetOrAddMultiplier(string key) { var kv = multipliers.Find(x => x.key == key); if (kv == null) { kv = new SerializableFloatKeyValue(key, 1.0f); multipliers.Add(kv); } return kv; } }
2. 使用SerializedProperty处理编辑器修改(推荐)
Unity编辑器推荐使用SerializedProperty来操作对象字段,它会自动处理序列化脏标记,确保修改被正确保存:
// Custom Editor Code: // 获取序列化对象的Modifiers属性 SerializedProperty modifiersProp = serializedObject.FindProperty("Modifiers"); for (int i = 0; i < modifiersProp.arraySize; i++) { SerializedProperty modifierProp = modifiersProp.GetArrayElementAtIndex(i); // 处理普通字段(modifierItemIdx和modifierItemID) SerializedProperty itemIdxProp = modifierProp.FindPropertyRelative("modifierItemIdx"); SerializedProperty itemIDProp = modifierProp.FindPropertyRelative("modifierItemID"); int newIdx = EditorGUILayout.Popup(new GUIContent("Item ID:"), itemIdxProp.intValue, itemTypes); if (newIdx != itemIdxProp.intValue) { itemIdxProp.intValue = newIdx; itemIDProp.stringValue = itemTypes[newIdx]; } // 处理multipliers列表中的Key1字段 SerializedProperty multipliersProp = modifierProp.FindPropertyRelative("multipliers"); int key1Index = -1; // 查找已存在的Key1条目 for (int j = 0; j < multipliersProp.arraySize; j++) { SerializedProperty kvProp = multipliersProp.GetArrayElementAtIndex(j); if (kvProp.FindPropertyRelative("key").stringValue == "Key1") { key1Index = j; break; } } // 如果不存在则新增条目 if (key1Index == -1) { multipliersProp.InsertArrayElementAtIndex(multipliersProp.arraySize); SerializedProperty newKvProp = multipliersProp.GetArrayElementAtIndex(multipliersProp.arraySize - 1); newKvProp.FindPropertyRelative("key").stringValue = "Key1"; newKvProp.FindPropertyRelative("value").floatValue = 1.0f; key1Index = multipliersProp.arraySize - 1; } // 修改Key1的值 SerializedProperty key1ValueProp = multipliersProp.GetArrayElementAtIndex(key1Index).FindPropertyRelative("value"); key1ValueProp.floatValue = EditorGUILayout.FloatField(new GUIContent("Key 1:"), key1ValueProp.floatValue); // 其他字段同理处理... } // 应用所有属性修改 serializedObject.ApplyModifiedProperties();
3. 若坚持直接修改字段,需手动标记脏状态
如果不想用SerializedProperty,直接修改字段后必须手动告诉Unity数据已更改,否则不会保存:
// Custom Editor Code: // 读取旧值并复制到新列表 var tmpModifiers = new List<MovementVariableModifierData>(Modifiers); for (int i = 0; i < tmpModifiers.Count; i++) { // 修改普通字段(正常工作部分保留) tmpModifiers[i].modifierItemIdx = EditorGUILayout.Popup(new GUIContent("Item ID:"), tmpModifiers[i].modifierItemIdx, itemTypes); tmpModifiers[i].modifierItemID = itemTypes[tmpModifiers[i].modifierItemIdx]; // 修改自定义列表中的值 var key1Kv = tmpModifiers[i].GetOrAddMultiplier("Key1"); key1Kv.value = EditorGUILayout.FloatField(new GUIContent("Key 1:"), key1Kv.value); // 更多字段修改... } Modifiers = tmpModifiers.ToArray(); // 标记对象为脏,触发保存 EditorUtility.SetDirty(target); // 若操作的是预制体实例,还需记录预制体修改 PrefabUtility.RecordPrefabInstancePropertyModifications(target);
关键注意点
- 永远不要依赖Unity序列化Dictionary,官方文档明确说明原生序列化不支持该类型。
- 使用
SerializedProperty是最稳妥的方式,能避免大部分序列化相关的问题。 - 手动修改字段后必须调用
EditorUtility.SetDirty,预制体场景还需额外调用PrefabUtility.RecordPrefabInstancePropertyModifications。
内容的提问来源于stack exchange,提问作者CHP
相关产品推荐
相关产品推荐

