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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 13:57:48