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); } } } }
注意事项
- 给每个ItemData设置唯一的itemId,避免不同物品重名导致加载错误;
- 确保ItemDatabase里的所有ItemData都正确关联了Sprite资源;
- 测试时可以打开
Application.persistentDataPath路径,查看存档文件里的itemIds是否正确保存。
内容的提问来源于stack exchange,提问作者Nequro
相关产品推荐
相关产品推荐

