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

Unity安卓物品栏存档读档异常求助:重启后物品UI丢失

问题根源

你遇到的核心问题是ScriptableObject无法通过JsonUtility正确序列化存档:ScriptableObject是Unity的资源对象,JsonUtility保存时只会记录它的内部引用ID,重启游戏后这些ID对应的实例无法被正确关联,导致物品列表里的Item变成无效引用,UI自然加载不到Sprite。

修复方案

改用「可序列化普通类+物品数据库」的方案,彻底解决资源引用的存档问题。


1. 替换Item为普通序列化类

把原来的ScriptableObject改成普通的可序列化类,让Json能完整保存物品数据的标识:

using UnityEngine;

[System.Serializable]
public class ItemData
{
    public string itemId; // 物品唯一标识(推荐用类似"potion_001"的格式,避免重名)
    public string name = "New Item";
    public bool isDefaultItem = false;

    // Use方法保留原逻辑,后续通过数据库关联Sprite
    public virtual void Use()
    {
        Debug.Log("Using " + name);
    }
}

2. 创建物品数据库管理资源

用ScriptableObject做物品数据库,统一管理所有物品的Sprite等资源:

using UnityEngine;
using System.Collections.Generic;

[CreateAssetMenu(fileName = "ItemDatabase", menuName = "Inventory/ItemDatabase")]
public class ItemDatabase : ScriptableObject
{
    public List<ItemData> allItems = new List<ItemData>();

    // 通过itemId查找对应物品的完整数据(包括Sprite)
    public ItemData GetItemById(string itemId)
    {
        return allItems.Find(item => item.itemId == itemId);
    }
}

操作步骤:在Project窗口右键创建Inventory/ItemDatabase,把所有物品的ItemData添加进去,给每个ItemData设置唯一itemId并关联Sprite。

3. 修改Inventory的存档逻辑

把原来保存Item引用的逻辑改成保存itemId,加载时从数据库获取完整数据:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

public class Inventory : MonoBehaviour
{
    #region Singleton
    public static Inventory Instance;
    private void Awake()
    {
        Instance = this;
        LoadInventory();
    }
    #endregion

    public delegate void OnItemChanged();
    public OnItemChanged onItemChangedCallback;

    public int slots = 20;
    public List<string> itemIds = new List<string>(); // 保存物品唯一ID,而非引用
    public ItemDatabase itemDatabase; // 在Inspector拖入创建好的物品数据库

    public bool Add(ItemData item)
    {
        if (itemIds.Count >= slots)
        {
            Debug.Log("Inventory is full");
            return false;
        }

        itemIds.Add(item.itemId);
        onItemChangedCallback?.Invoke();
        SaveInventory();
        return true;
    }

    public void Remove(ItemData item)
    {
        itemIds.Remove(item.itemId);
        onItemChangedCallback?.Invoke();
        SaveInventory();
    }

    // 获取当前物品栏的完整物品数据
    public List<ItemData> GetCurrentItems()
    {
        List<ItemData> items = new List<ItemData>();
        foreach (string id in itemIds)
        {
            ItemData item = itemDatabase.GetItemById(id);
            if (item != null) items.Add(item);
        }
        return items;
    }

    // 以下方法逻辑修改为操作itemIds
    public void RemoveAllItems()
    {
        itemIds.Clear();
        onItemChangedCallback?.Invoke();
        SaveInventory();
    }

    public void ClearInventory()
    {
        itemIds.Clear();
        onItemChangedCallback?.Invoke();
        SaveInventory();
    }

    public void SellRandomValue()
    {
        int coinValue = PlayerPrefs.GetInt("Coins");
        int randomCoin = 0;

        if (itemIds.Count >= 1) randomCoin = Random.Range(1, 5);
        if (itemIds.Count >= 5) randomCoin = Random.Range(5, 15);
        if (itemIds.Count >= 10) randomCoin = Random.Range(10, 25);

        coinValue += randomCoin;
        PlayerPrefs.SetInt("Coins", coinValue);
    }

    // 存档读档逻辑不变,因为现在序列化的是字符串列表,JsonUtility可以正确处理
    public void SaveInventory()
    {
        string json = JsonUtility.ToJson(this);
        string filePath = Path.Combine(Application.persistentDataPath, "inventory_save.txt");
        File.WriteAllText(filePath, json);
        Debug.Log("Inventory saved to " + filePath);
    }

    public void LoadInventory()
    {
        string filePath = Path.Combine(Application.persistentDataPath, "inventory_save.txt");
        if (File.Exists(filePath))
        {
            string json = File.ReadAllText(filePath);
            JsonUtility.FromJsonOverwrite(json, this);
            Debug.Log("Inventory loaded from " + filePath);
        }
        else
        {
            Debug.Log("No inventory save file found");
        }
    }
}

4. 适配InventoryUI和InventorySlot

修改UI相关代码,适配新的ItemData和物品获取逻辑:

InventoryUI修改UpdateUI方法

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InventoryUI : MonoBehaviour
{
    public Transform itemsParent;
    Inventory inventory;
    InventorySlot[] slots;
    EquipmentManager equipmentManager;

    void Start()
    {
        equipmentManager = EquipmentManager.Instance;
        equipmentManager.LoadEquipment();

        inventory = Inventory.Instance;
        inventory.onItemChangedCallback += UpdateUI;

        slots = itemsParent.GetComponentsInChildren<InventorySlot>();
        UpdateUI();
    }

    void UpdateUI()
    {
        List<ItemData> currentItems = inventory.GetCurrentItems();
        for (int i = 0; i < slots.Length; i++)
        {
            if (i < currentItems.Count)
            {
                slots[i].AddItem(currentItems[i]);
            }
            else
            {
                slots[i].ClearSlot();
            }
        }
    }
}

InventorySlot适配ItemData

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(AudioSource))]
public class InventorySlot : MonoBehaviour
{
    public Image icon;
    public Button removeButton; 
    ItemData item;

    AudioSource audioSource;
    public AudioClip UseItemClip;

    void Start()
    {
        audioSource = GetComponent<AudioSource>();
    }

    public void AddItem(ItemData newItem)
    {
        item = newItem;
        // 从数据库获取Sprite(这里直接用newItem的icon,因为数据库里的ItemData已经关联了Sprite)
        icon.sprite = newItem.icon;
        icon.enabled = true;
        removeButton.interactable = true;
    }

    public void ClearSlot()
    {
        item = null;
        icon.sprite = null;
        icon.enabled = false;
        removeButton.interactable = false;
    }

    public void OnRemoveButton()
    {
        Inventory.Instance.Remove(item);
    }

    public void RemoveAll()
    {
        Inventory.Instance.ClearInventory();
    }

    public void UseItem()
    {
        if (item != null) 
        {
            audioSource.PlayOneShot(UseItemClip);
            item.Use();
        }
    }

    public void TransferToChest()
    {
        if (item != null)
        {
            if (Chest.Instance.Add(item))
            {
                Inventory.Instance.Remove(item);
            }
        }
    }
}

注意事项
  1. 给每个ItemData设置唯一的itemId,避免不同物品重名导致加载错误;
  2. 确保ItemDatabase里的所有ItemData都正确关联了Sprite资源;
  3. 测试时可以打开Application.persistentDataPath路径,查看存档文件里的itemIds是否正确保存。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 13:20:04