Unity中的序列化数据丢失

本文探讨了Unity中Asset数据结构序列化导致的数据丢失问题,并通过具体案例分析了问题产生的原因。作者提出了一些避免序列化数据丢失的方法,包括如何正确使用泛型类、嵌套数组结构及不可序列化的数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unity中的序列化数据丢失

问题描述

        近期在项目制作中,需要运用Asset进行数据结构的实例化存储和使用,在制作编辑器的过程中发现一个十分尴尬的问题,在某些情况下会发生代码修改后通过编辑器配置的某些数据会丢失,我把这种问题叫做“序列化数据丢失”(因为我也不知道怎么称呼它)。

通过一个简单的示例可以体现出该问题:

public string[][] twoString = new string[1][] ;

以上为一个二维数组的结构体,对其进行赋值后保存成Asset文件。在不改动源代码的情况下,配置进入的数据可以正常的读取并且不会丢失,但当我们对任意文件进行代码修改并保存后(工程对代码进行重新编译)。再次读取该Asset文件会发现数据全部丢失。

问题测试

我对该问题进行了各种可能性的测试寻找会出现序列化数据冲刷现象的情况,如下数据测试用例:

// 失败的方式

    public TDictionary<string, string> testT=new TDictionary<string, string>();

    // 正确的方式

    public TestDictionary testDictionary = new TestDictionary();

    // 正确的方式

    public List<string> testString = new List<string>();

    // 失败的方式

    public string[][] twoString = new string[1][] ;

    // 正确的方式

    public DepthDictionary depth = new DepthDictionary();

    // 失败的方式

    public DepthErrDictionary depthErr = new DepthErrDictionary();

        其中TDictionary使用的是第三方编写的继承自IDictionary实现的字典数据结构,该第三方框架地址: https://github.com/neuecc/SerializableDictionary

Depth相关数据结构:

    [Serializable]

    public class PathItem

    {

        public PathItem(string path)

        {

            this.path = path;

        }

        // 路径

        public string path;

    }

    [Serializable]

    public class PathsList

    {

        public PathsList(string path)

        {

            psList.Add(new PathItem(path));

        }

        public List<PathItem> psList = new List<PathItem>();

    }

    [Serializable]

    public class DepthDictionary : AssetDictionary

    {

        public PathsList[] list;

        public DepthDictionary()

        {

            list = new PathsList[MAX];

        }

        protected override T[] GetValues<T>()

        {

            return list as T[];

        }

        protected override void SetValues<T>(T[] values)

        {

            list = values as PathsList[];

        }

    }

    [Serializable]

    public class DepthErrDictionary : AssetDictionary

    {

        public List<PathItem>[] list;

        public DepthErrDictionary()

        {

            list = new List<PathItem>[MAX];

        }

        protected override T[] GetValues<T>()

        {

            return list as T[];

        }

        protected override void SetValues<T>(T[] values)

        {

            list = values as List<PathItem>[];

        }

}

问题解决

通过测试用例大体总结出来的会导致数据冲刷的现象出现在如下几点:

1. 泛型类的使用,但是注意如果直接使用List<T>是不会发生问题,注:T需满足当前所述几点条件。

2. 嵌套的数组结构,无论是[][]还是List<T>[]都会产生问题。

3. 不可序列化的数据结构(如Dictionary)。

所以遵循以上几点去实现数据结构将规避该问题的发生,其中一个重要的是Dictionary的支持,以及多维数组的支持。

多为数组的方案在上述测试DepthDictionary中应用并演示,使用嵌套对象的方式解决,结果如图:


提供一份简单的Dictionary实现例子,本例采用哈希冲突的线性探测方式实现,以供参考。

public abstract class AssetDictionary

    {

        protected string[] keys;

        // 线性探索表的块大小

        protected int M = 16;

        // 数据块幂数

        protected const int N = 8;

        protected int ADDI = 0;

        // 2^N

        protected int MAX;

        protected int KEYS_COUNT = 0;

        protected int lenght = 0;

        public int Count {

            get { return lenght; }

        }

        protected abstract T[] GetValues<T>();

        protected abstract void SetValues<T>(T[] values);

        public AssetDictionary()

        {

            MAX = (int)Math.Pow(2, N);

            keys = new string[MAX];

        }

        public void Add<T>(string key,T value)

        {

            int index = key.GetHashCode() % MAX;

            // **数据

            for (int i = 0; i < M; i++)

            {

                if (index == MAX)

                    index = 0;

                if (keys[index] != null && keys[index] != key)

                    continue;

                keys[index] = key;

                GetValues<T>()[index] = value;

                lenght++;

                return;

            }

            // 重构数据块大小

            resetBlock<T>(null,null);

            Add<T>(key, value);

        }

        public bool Remove<T>(string key)

        {

            int index = key.GetHashCode() % MAX;

            // **数据

            for (int i = 0; i < M; i++)

            {

                if (index == MAX)

                    index = 0;

                if (keys[index] == key)

                {

                    keys[index] = null;

                    GetValues<T>()[index] = default(T);

                    lenght--;

                    return true;

                }

            }

            return false;

        }

        public T GetValue<T>(string key)

        {

            int index = key.GetHashCode() % MAX;

            if(keys[index] == null)

                return default(T);

            // **数据

            for (int i = 0; i < M; i++)

            {

                if (index == MAX)

                    index = 0;

                if (keys[index] == key)

                    return GetValues<T>()[index];

            }

            return default(T);

        }

        protected void resetBlock<T>(string[] copyKeys,T[] copyValues)

        {

            // 增加新的区块

            ADDI += 1;

            MAX = (int)Math.Pow(2, N + ADDI);

            M = (int)Math.Pow(2, 4 + ADDI);

            // 原始数据空间

            if(copyKeys == null)

                copyKeys = keys;

            if(copyValues == null)

                copyValues = GetValues<T>();

            // 覆盖重置空间

            keys = new string[MAX];

            SetValues<T>(new T[MAX]);

            // 重构空间,根据目前持有的key进行

            for(int i = 0; i < copyKeys.Length; i++)

            {

                if(copyKeys[i] != null)

                {

                    if(AddBlockValue<T>(copyKeys[i], copyValues[i]) == false)

                    {

                        // 列表超出最大范围

                        resetBlock<T>(copyKeys,copyValues);

                        return;

                    }

                }

            }

        }

        protected bool AddBlockValue<T>(string key,T value)

        {

            int index = key.GetHashCode() % MAX;

            // **数据

            for (int i = 0; i < M; i++)

            {

                if (index == MAX)

                    index = 0;

                if (keys[index] != null)

                    continue;

                keys[index] = key;

                GetValues<T>()[index] = value;

                return true;

            }

            return false;

        }

}

使用中需要继承AssetDictionary并实现如上边提到的数据结构中Depth相关的代码。

<think>我们正在处理用户关于Unity序列化使用方法的问题。根据用户要求,我们需要提供清晰的结构化教程,并参考提供的引用内容。参考引用: [^1]:介绍了Unity序列化机制的重要性,贯穿资源管理、编辑器功能和运行时实现。 [^2]:提到Unity提供了多种序列化方法,包括JsonUtility、System.Serializable、BinaryFormatter和第三方库如Newtonsoft.Json。 [^3]:详细讨论了Unity序列化的各个方面,包括游戏对象序列化、脚本变量序列化(使用[SerializeField]和[System.Serializable])、ScriptableObject以及常见序列化格式(JSON、CSV、Excel、XML)。用户问题:Unity序列化使用方法教程我们将按照以下结构组织回答:1.序列化基础概念(简要)2. Unity内置序列化方法-使用[SerializeField]序列化私有字段-使用[System.Serializable]序列化自定义类- ScriptableObject的使用3.常用序列化格式及实现- JSON序列化(使用UnityEngine.JsonUtility和Newtonsoft.Json)- XML序列化-其他格式(简要)4.序列化实践示例注意:在代码示例中,我们将使用C#语言,并确保代码正确性。开始组织回答:</think>### Unity序列化使用方法教程 #### 一、序列化基础概念 序列化是将对象状态转换为可存储或传输格式的过程,反序列化则是逆向操作。在Unity中,序列化机制贯穿资源管理、编辑器功能和运行数据持久化[^1]。核心作用包括: - 保存游戏状态(如存档) - 编辑器属性持久化 - 网络数据传输 - 脚本配置存储 #### 二、Unity内置序列化方法 1. **`[SerializeField]`属性** 序列化私有字段(默认仅序列化public字段): ```csharp public class Player : MonoBehaviour { [SerializeField] private int health; // 私有字段可序列化 } ``` 2. **`[System.Serializable]`属性** 使自定义类/结构体可序列化: ```csharp [System.Serializable] public class Item { public string name; public int id; } public class Inventory : MonoBehaviour { public List<Item> items; // 自定义类列表可序列化 } ``` 3. **ScriptableObject数据容器** 创建可重复使用的数据资源: ```csharp [CreateAssetMenu] public class WeaponData : ScriptableObject { public int damage; public float range; } ``` 使用方法: - Assets右键 → Create → WeaponData - 拖拽到脚本的public字段 #### 三、常用序列化格式实践 1. **JSON序列化**(推荐轻量级数据) ```csharp // UnityEngine内置方法 string json = JsonUtility.ToJson(playerData); PlayerData data = JsonUtility.FromJson<PlayerData>(json); // Newtonsoft.Json(需导入包,支持复杂类型) string json = Newtonsoft.Json.JsonConvert.SerializeObject(data); ``` > 优点:易读性强,兼容性好[^3] 2. **XML序列化**(需命名空间`System.Xml.Serialization`) ```csharp XmlSerializer serializer = new XmlSerializer(typeof(PlayerData)); using (StringWriter writer = new StringWriter()) { serializer.Serialize(writer, data); string xml = writer.ToString(); } ``` 3. **二进制序列化**(紧凑但不易读) ```csharp BinaryFormatter formatter = new BinaryFormatter(); MemoryStream stream = new MemoryStream(); formatter.Serialize(stream, data); ``` #### 四、最佳实践指南 1. **编辑器集成技巧** - 用`[HideInInspector]`隐藏不需要序列化的public字段 - 用`[Range(1,100)]`等Attribute约束序列化值范围 2. **版本兼容性处理** ```csharp [System.Serializable] public class SaveData { [FormerlySerializedAs("oldHealth")] // 字段重命名兼容 public int currentHealth; } ``` 3. **性能优化** - 避免每帧序列化:使用缓存或按需触发 - 大型数据用异步序列化防止卡顿 #### 五、调试与常见问题 - **查看序列化数据**:在Inspector窗口展开脚本组件 - **字段未显示**:检查是否缺少`[SerializeField]`或访问修饰符 - **数据丢失**:确保使用`PlayerPrefs`保存或持久化存储 > 完整示例工程:[Unity官方序列化示例](https://github.com/Unity-Technologies/SerializationExamples)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值