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

Unity修改库存ScriptableObject数据时同步改动源资产的问题排查

问题原因

你的判断完全正确,核心问题就是库存列表直接存储了原始ScriptableObject资产的引用,没有做数据隔离。

  • ScriptableObject是引用类型的项目资源,你传入AddItem的参数本身就是指向磁盘上.asset文件的直接引用,不是独立的数据副本。
  • 后续RemoveItem里修改searchedItem.amount时,改动直接作用在原始资源上,Unity触发资源序列化后,改动就会永久写入资产文件,就出现了你看到的源文件数值被改动的现象。
  • 顺带提一句你现有AddItem的堆叠逻辑有bug:遍历库存列表时,只要当前遍历项和新物品不重名就会直接插入新条目,会导致同一个可堆叠物品在库存里重复生成多条记录。
修复方案

核心思路是把静态配置数据和运行时库存数据做隔离,不要直接修改原始资产,推荐两种实现:

方案1:拆分配置类和运行时库存条目(推荐)

这是Unity项目里的标准实现方式,结构清晰也不容易出资源修改的问题:

  1. 把原有Item类改成纯静态配置类,删掉所有运行时会变动的字段(比如amount),只保留物品名、类型、描述、是否可堆叠、图标这类固定不变的配置属性。
  2. 新建普通C#类作为运行时库存条目,专门存动态变动的数据:
[System.Serializable]
public class InventoryEntry
{
    public Item config; // 只读,指向静态配置资产,永远不修改
    public int count; // 运行时变动的数量存在这里
}
  1. 把库存列表类型改为List<InventoryEntry>,重写增删逻辑:
private List<InventoryEntry> itemList = new List<InventoryEntry>();

public void AddItem(Item targetItem, int addCount = 1)
{
    if (targetItem.isStackable)
    {
        InventoryEntry existEntry = itemList.Find(entry => entry.config.name == targetItem.name);
        if (existEntry != null)
        {
            existEntry.count += addCount;
            return;
        }
    }
    // 没有可堆叠的现有条目就新建运行时实例
    itemList.Add(new InventoryEntry
    {
        config = targetItem,
        count = addCount
    });
}

public void RemoveItem(Item targetItem, int removeCount = 1)
{
    InventoryEntry existEntry = itemList.Find(entry => entry.config.name == targetItem.name);
    if (existEntry == null) return;

    existEntry.count -= removeCount;
    if (existEntry.count <= 0)
    {
        itemList.Remove(existEntry);
    }
}

这种实现下所有动态修改都发生在运行时生成的普通类实例上,完全不会触碰原始资产文件,也不用额外处理资源销毁的问题。

方案2:运行时复制ScriptableObject资产(快速修复)

如果不想改动现有数据结构,可以在物品入库存时调用Instantiate创建一份原始资产的运行时副本,后续所有修改都在副本上操作,和原始资产解耦:

public void AddItem(Item item)
{
    // 创建运行时副本,脱离原始资产引用
    Item runtimeItem = Instantiate(item);
    
    if (!runtimeItem.isStackable || itemList.Count == 0)
    {
        itemList.Add(runtimeItem);
        return;
    }

    Item existItem = itemList.Find(i => i.name == runtimeItem.name);
    if (existItem != null)
    {
        existItem.amount += runtimeItem.amount;
        Destroy(runtimeItem); // 销毁临时副本避免内存泄漏
        return;
    }
    itemList.Add(runtimeItem);
}

注意这种方式要在物品移除、场景卸载时手动Destroy掉运行时生成的ScriptableObject副本,否则会造成内存残留,适合小项目快速修复问题用。

内容的提问来源于stack exchange,提问作者Suspense

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 09:39:25