Unity自定义Property Drawer进入Play模式后内容无法保存
问题修复方案:Unity自定义Property Drawer数据无法跨Play模式保存
问题根源
- 直接通过反射修改对象实例,未通过Unity的
SerializedProperty系统,导致序列化数据未被正确记录 CustomGrid和Tile中的属性使用私有setter,未显式标记可序列化,Unity无法持久化这些数据- 已废弃的
EditorUtility.SetDirty无法可靠触发场景/对象的脏标记
具体修复步骤
1. 重构Tile类,确保字段可序列化
将自动属性改为带[SerializeField]的私有字段,让Unity能正确识别并保存数据:
[System.Serializable] public class Tile { [SerializeField] private int _x; [SerializeField] private int _y; [SerializeField] private bool _isAvailable = true; public int X => _x; public int Y => _y; public bool IsAvailable => _isAvailable; public Tile() { } public Tile(int x, int y) { _x = x; _y = y; } public void ToggleAvailability() { _isAvailable = !_isAvailable; } }
2. 重构CustomGrid类,适配Unity序列化规则
将属性改为带[SerializeField]的字段,替换私有setter,确保泛型类的序列化兼容性:
using System.Collections.Generic; using UnityEngine; [System.Serializable] public class CustomGrid<T> where T : Tile { [SerializeField] private T[,] _tiles; [SerializeField] private int _xLength; [SerializeField] private int _yLength; public T[,] Tiles => _tiles; public int XLength => _xLength; public int YLength => _yLength; public CustomGrid(int xLength, int yLength) { _xLength = xLength; _yLength = yLength; _tiles = new T[xLength, yLength]; for (int x = 0; x < xLength; x++) { for (int y = 0; y < yLength; y++) { _tiles[x, y] = new Tile(x, y) as T; } } } public List<T> GetNeighbouringTilesFromTile(T tile) { List<T> neighbours = new List<T>(); if (tile.X > 0) neighbours.Add(_tiles[tile.X - 1, tile.Y]); if (tile.X < _xLength - 1) neighbours.Add(_tiles[tile.X + 1, tile.Y]); if (tile.Y > 0) neighbours.Add(_tiles[tile.X, tile.Y - 1]); if (tile.Y < _yLength - 1) neighbours.Add(_tiles[tile.X, tile.Y + 1]); return neighbours; } }
3. 修改GridPropertyDrawer,使用SerializedProperty操作数据
放弃直接反射修改对象,改用Unity官方序列化API,确保数据修改被正确记录:
using UnityEditor; using UnityEngine; [CustomPropertyDrawer(typeof(CustomGrid<Tile>))] public class GridPropertyDrawer : PropertyDrawer { private int _tempYLength = -1; private int _tempXLength = -1; private float _padding = 5; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); // 获取CustomGrid的序列化字段 var tilesProp = property.FindPropertyRelative("_tiles"); var xLengthProp = property.FindPropertyRelative("_xLength"); var yLengthProp = property.FindPropertyRelative("_yLength"); // 初始化临时长度 if (_tempXLength < 0) { _tempXLength = xLengthProp.intValue; } if (_tempYLength < 0) { _tempYLength = yLengthProp.intValue; } // 折叠面板 property.isExpanded = EditorGUILayout.Foldout(property.isExpanded, "Grid"); if (property.isExpanded) { EditorGUI.indentLevel++; // 显示行列输入框 _tempYLength = EditorGUILayout.IntField("Row(s)", _tempYLength); _tempXLength = EditorGUILayout.IntField("Column(s)", _tempXLength); GUILayout.Space(10); bool hasValidGrid = xLengthProp.intValue > 0 && yLengthProp.intValue > 0 && tilesProp != null && tilesProp.arraySize > 0; if (!hasValidGrid) { if (_tempYLength > 0 && _tempXLength > 0) { if (GUILayout.Button("Create Grid")) { var newGrid = new CustomGrid<Tile>(_tempXLength, _tempYLength); fieldInfo.SetValue(property.serializedObject.targetObject, newGrid); property.serializedObject.Update(); // 标记场景/Prefab为脏 EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); if (PrefabUtility.IsPartOfPrefabInstance(property.serializedObject.targetObject)) { PrefabUtility.RecordPrefabInstancePropertyModifications(property.serializedObject.targetObject); } } } } else { float inspectorWidth = EditorGUIUtility.currentViewWidth; float tileWidth = Mathf.Clamp((inspectorWidth / xLengthProp.intValue) - _padding, 5, 40); float tileHeight = tileWidth; EditorGUI.indentLevel--; // 遍历序列化的二维数组(Unity按行优先存储为一维数组) for (int y = 0; y < yLengthProp.intValue; y++) { EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); for (int x = 0; x < xLengthProp.intValue; x++) { int arrayIndex = y * xLengthProp.intValue + x; var tileProp = tilesProp.GetArrayElementAtIndex(arrayIndex); var isAvailableProp = tileProp.FindPropertyRelative("_isAvailable"); GUI.color = isAvailableProp.boolValue ? Color.white : Color.gray; if (GUILayout.Button("", GUILayout.Width(tileWidth), GUILayout.Height(tileHeight))) { isAvailableProp.boolValue = !isAvailableProp.boolValue; property.serializedObject.ApplyModifiedProperties(); // 标记脏状态 EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); if (PrefabUtility.IsPartOfPrefabInstance(property.serializedObject.targetObject)) { PrefabUtility.RecordPrefabInstancePropertyModifications(property.serializedObject.targetObject); } } GUI.color = Color.white; } GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); } EditorGUI.indentLevel++; if (_tempYLength != yLengthProp.intValue || _tempXLength != xLengthProp.intValue) { GUILayout.Space(10); if (GUILayout.Button("Update Grid")) { var newGrid = new CustomGrid<Tile>(_tempXLength, _tempYLength); fieldInfo.SetValue(property.serializedObject.targetObject, newGrid); property.serializedObject.Update(); // 标记脏状态 EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); if (PrefabUtility.IsPartOfPrefabInstance(property.serializedObject.targetObject)) { PrefabUtility.RecordPrefabInstancePropertyModifications(property.serializedObject.targetObject); } } } } EditorGUI.indentLevel--; } property.serializedObject.ApplyModifiedProperties(); EditorGUI.EndProperty(); } }
关键修复说明
- 序列化兼容性:用
[SerializeField]标记所有需要持久化的字段,确保Unity能识别并保存数据 - 官方API操作:通过
SerializedProperty访问和修改数据,让Unity序列化系统正确记录变更 - 脏状态标记:使用
EditorSceneManager.MarkSceneDirty和PrefabUtility.RecordPrefabInstancePropertyModifications分别处理场景对象和Prefab实例的保存触发 - 废弃API替换:移除已过时的
EditorUtility.SetDirty,改用更可靠的现代API
修改完成后,网格配置数据会在Play模式切换时被正确保存,退出Play模式后数据不会丢失。
内容的提问来源于stack exchange,提问作者Tyler Jay
相关产品推荐
相关产品推荐

