Unity中使用JsonUtility序列化含Scriptable Object的结构体列表失败问题
问题
我是Unity和C#新手,尝试用JsonUtility序列化包含Scriptable Object(ItemData)的结构体(ItemInfo)列表,但序列化后的JSON只记录m_FileID和m_PathID,反序列化时返回null。
序列化后的JSON片段:
"Items":[{"ItemData":{"m_FileID":-794,"m_PathID":0},"ID":1,"Name":"Cube","Value":140.0}]
相关代码如下:
Manager类
using System; using System.Collections.Generic; using System.IO; [Serializable] public class Manager{ public List<ItemInfo> Items; public void Save() { string path = Application.persistentDataPath; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } string text = JsonUtility.ToJson(new SaveGameData { Items = this.Items }, true); File.WriteAllText(path + "/save", text); } public void Load() { SaveGameData data = JsonUtility.FromJson<SaveGameData>(File.ReadAllText(Application.persistentDataPath + "/save")); this.Items = data.Items; } }
SaveGameData类
using System; using System.Collections.Generic; [Serializable] public class SaveGameData { public List<ItemInfo> ShopItems; }
ItemInfo结构体
using System; [Serializable] public struct ItemInfo { public ItemData Data; public uint ID; public string Name; public float Value; }
ItemData类(ScriptableObject)
using System; using UnityEngine; [CreateAssetMenu(fileName = "ItemData", menuName = "Menu/ItemData")] public class ItemData: ScriptableObject { //getters and setters public string description; public int level; public string rarity; }
请问如何像普通对象一样序列化Scriptable Object?
解决方案
Unity的JsonUtility对ScriptableObject的默认序列化是引用式的,只会存储其在Unity资源系统中的ID(也就是你看到的m_FileID和m_PathID),这种方式只适合Unity内部资源引用,无法直接作为普通对象序列化到JSON。要实现预期效果,需要通过以下步骤调整:
1. 创建ScriptableObject的数据传输对象(DTO)
定义一个与ItemData字段完全一致的普通可序列化类,用来存储ItemData的实际数据:
[Serializable] public class ItemDataDTO { public string description; public int level; public string rarity; // 从ItemData实例转换为DTO public static ItemDataDTO FromItemData(ItemData data) { if (data == null) return null; return new ItemDataDTO { description = data.description, level = data.level, rarity = data.rarity }; } // 从DTO转换回ItemData(加载后重建ScriptableObject用) public ItemData ToItemData() { ItemData data = ScriptableObject.CreateInstance<ItemData>(); data.description = description; data.level = level; data.rarity = rarity; return data; } }
2. 修改ItemInfo结构体,替换为DTO
把ItemInfo中的ItemData引用换成ItemDataDTO,并添加数据转换方法:
[Serializable] public struct ItemInfo { public ItemDataDTO Data; // 替换为DTO类型 public uint ID; public string Name; public float Value; // 从原ItemInfo转换为带DTO的版本 public static ItemInfo FromOriginal(ItemInfo original) { return new ItemInfo { Data = ItemDataDTO.FromItemData(original.Data), ID = original.ID, Name = original.Name, Value = original.Value }; } // 从DTO版本转换回原ItemInfo(加载时用) public ItemInfo ToOriginal() { return new ItemInfo { Data = Data?.ToItemData(), ID = ID, Name = Name, Value = Value }; } }
3. 调整Save/Load逻辑,适配数据转换
修改Manager类的序列化/反序列化逻辑,在保存前转换为DTO格式,加载后再转换回原结构:
using System.Linq; // 需要引入Linq用于集合转换 public void Save() { string path = Application.persistentDataPath; if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } // 转换原Items为带DTO的列表 var saveItems = Items.Select(ItemInfo.FromOriginal).ToList(); // 注意修正字段名匹配:SaveGameData的字段是ShopItems string text = JsonUtility.ToJson(new SaveGameData { ShopItems = saveItems }, true); File.WriteAllText(Path.Combine(path, "save"), text); } public void Load() { string filePath = Path.Combine(Application.persistentDataPath, "save"); if (!File.Exists(filePath)) return; SaveGameData data = JsonUtility.FromJson<SaveGameData>(File.ReadAllText(filePath)); // 转换DTO列表回原ItemInfo列表 this.Items = data.ShopItems.Select(item => item.ToOriginal()).ToList(); }
关键注意点
- 原代码存在字段名不匹配问题:
SaveGameData的字段是ShopItems,但Manager.Save()中错误赋值为Items = this.Items,需要修正为ShopItems = saveItems,否则序列化后JSON不会包含正确的物品数据。 - 如果你的
ItemData是从Resources/Addressables加载的资源,加载后也可以通过DTO的字段(如level、rarity)匹配已加载的实例,无需重建ScriptableObject。
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

