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

Unity开发中存档ItemSlot数组为null且报空引用异常的问题

核心错误定位

直接触发空引用的原因

  • PlayerData 中声明的 ItemSlot[] InventorySlots 从未初始化,默认值为 null,执行数组索引赋值操作时直接抛出 NullReferenceException。
  • LoopThroughItemSlots 函数逻辑完全失效:foreach 内部第一次迭代就执行 return slot,后续的 break 属于永远不会执行的死代码,无论调用多少次都只会返回库存列表的第一个槽位,完全达不到遍历全量槽位的效果。

存档逻辑的底层设计缺陷

  • ItemSlot 是挂载在UI槽位上的 MonoBehaviour 组件,关联了大量Unity引擎内部引用(GameObject、RectTransform、UI渲染组件等),BinaryFormatter 无法正确序列化这类Unity对象,哪怕空引用问题解决,序列化结果也只会是空引用,对应库存数据完全丢失。
  • PlayerData 中错误存储了 InventoryUI 场景组件引用,这类运行时场景对象不属于纯存档数据,序列化时会直接触发异常。
  • 读档逻辑直接将序列化对象替换 itemSlotList,但 itemSlotList 存储的是当前场景中已存在的、绑定了UI元素的 ItemSlot 实例,直接替换会导致UI与数据完全脱节,界面不会更新。
  • 存档时使用 FileMode.CreateNew,当存档文件已存在时会直接抛出IOException,无法覆盖写入。
可运行的修复方案

1. 定义纯库存数据类

新建仅存储核心信息的可序列化类,完全避开Unity对象的序列化限制:

[System.Serializable]
public class InventorySlotData
{
    // 用全局唯一物品ID代替直接存储物品实例/ScriptableObject
    public int ItemID;
    public int StackCount;

    public InventorySlotData() {}
    public InventorySlotData(int itemID, int stackCount)
    {
        ItemID = itemID;
        StackCount = stackCount;
    }
}

2. 重构PlayerData类

移除无效的场景组件引用,修正数组初始化逻辑与库存遍历逻辑:

[System.Serializable]
public class PlayerData 
{
    // 生命值相关变量
    public float Health;
    public float MaxHealth;

    // 体力相关变量
    public float Stamina;
    public float MaxStamina;
    public float MovementSpeedWhileSprinting;
    public float MovementSpeedWhileWalking;
    public float StaminaDownWhileSprinting;
    public float StaminaAddWhileSprinting;

    // 改用纯数据数组存储库存,替换原有的ItemSlot数组
    public InventorySlotData[] InventorySlots;

    public PlayerData(Health health, Sprinting sprinting, InventoryUI invUI) 
    {
        Health = health.health;
        MaxHealth = health.maxHealth;
        Stamina = sprinting.Stamina;
        MaxStamina = sprinting.MaxStamina;
        MovementSpeedWhileSprinting = sprinting.MovementSpeedWhileSprinting;
        MovementSpeedWhileWalking = sprinting.MovementSpeedWhileWalking;
        StaminaDownWhileSprinting = sprinting.StaminaDownWhileSprinting;
        StaminaAddWhileSprinting = sprinting.StaminaAddWhileSprinting;

        SaveInventory(invUI);
    }

    private void SaveInventory(InventoryUI invUI) 
    {
        // 根据当前库存槽位数量初始化数组,解决空引用问题
        InventorySlots = new InventorySlotData[invUI.itemSlotList.Count];
        for (int i = 0; i < invUI.itemSlotList.Count; i++) 
        {
            ItemSlot currentSlot = invUI.itemSlotList[i];
            // 仅提取核心数据存入纯数据类,不直接存储ItemSlot实例
            InventorySlots[i] = new InventorySlotData(
                currentSlot.CurrentItem.ItemID, 
                currentSlot.StackCount
            );
        }
    }
}

3. 修正SaveAndLoad存档逻辑

替换文件打开模式,避免重复存档时报错:

public static void Save(Health health, Sprinting sprinting, InventoryUI invUI) 
{
    BinaryFormatter formatter = new BinaryFormatter();
    string path = Application.persistentDataPath + "/player.AutoBox";
    // 用FileMode.Create覆盖已有存档,替换原有的FileMode.CreateNew
    using FileStream stream = new FileStream(path, FileMode.Create);
    PlayerData data = new PlayerData(health, sprinting, invUI);
    formatter.Serialize(stream, data);
}

4. 修正读档逻辑

不再直接替换itemSlotList,而是将存档数据赋值给当前场景已存在的槽位实例,保证UI正常绑定:

public void Load() 
{
    string savePath = Application.persistentDataPath + "/player.AutoBox";
    if (!File.Exists(savePath)) 
        return;

    PlayerData data = SaveAndLoad.Load();
    // 生命值、体力相关的赋值逻辑保持不变
    health.health = data.Health;
    health.maxHealth = data.MaxHealth;
    sprinting.Stamina = data.Stamina;
    sprinting.MaxStamina = data.MaxStamina;
    sprinting.MovementSpeedWhileSprinting = data.MovementSpeedWhileSprinting;
    sprinting.MovementSpeedWhileWalking = data.MovementSpeedWhileWalking;
    sprinting.StaminaDownWhileSprinting = data.StaminaDownWhileSprinting;
    sprinting.StaminaAddWhileSprinting = data.StaminaAddWhileSprinting;

    // 库存读档:遍历当前已存在的UI槽位,写入存档数据
    InventoryUI invUI = Inventory.instance.InvUI;
    for (int i = 0; i < data.InventorySlots.Length; i++)
    {
        InventorySlotData slotData = data.InventorySlots[i];
        // 通过全局物品ID数据库查询对应物品实例,避免直接序列化物品资产
        ItemSO item = ItemDatabase.instance.GetItemByID(slotData.ItemID);
        invUI.itemSlotList[i].UpdateSlotData(item, slotData.StackCount);
    }
}

冗余代码清理

直接删除逻辑完全错误的LoopThroughItemSlots函数。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 08:45:08