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

Unity自定义Property Drawer进入Play模式后内容无法保存

问题修复方案:Unity自定义Property Drawer数据无法跨Play模式保存

问题根源

  1. 直接通过反射修改对象实例,未通过Unity的SerializedProperty系统,导致序列化数据未被正确记录
  2. CustomGrid和Tile中的属性使用私有setter,未显式标记可序列化,Unity无法持久化这些数据
  3. 已废弃的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:24:58