Unity类银河战士恶魔城学习总结(P143 Save Inventory System 保存仓库中的物品)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili

教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/

本章节实现了仓库物品的保存

SerializableDictionary.cs

Unity 中的序列化问题

  1. 标准字典无法序列化 :Unity 的序列化系统(如 Inspector 显示或存档功能)不支持 Dictionary<TKey, TValue>,因为它无法直接保存键值对的关系。
  2. 解决方法 :通过两个列表(keysvalues)分别保存字典的键和值,再通过自定义逻辑将列表和字典互相转换。

序列化前处理 (OnBeforeSerialize)

  • 触发时机:当 Unity 序列化对象时(如保存场景或进入播放模式)。
  • 作用
    1. 清空现有的 keysvalues 列表。
    2. 遍历字典中的所有键值对,将键存入 keys 列表,值存入 values 列表。
  • 结果:字典中的数据被转换为两个可序列化的列表。

反序列化后处理 (OnAfterDeserialize)

  • 触发时机:当 Unity 反序列化对象时(如加载场景或从存档中还原数据)。
  • 作用
    1. 清空字典,确保它是空的。
    2. 检查 keysvalues 列表的长度是否一致。如果不一致,打印错误信息。
    3. 遍历 keysvalues,将键值对逐一添加到字典中。
  • 结果:列表中的数据被还原到字典中。
cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, ISerializationCallbackReceiver
{
    [SerializeField] private List<TKey> keys = new List<TKey>();
    [SerializeField] private List<TValue> values = new List<TValue>();

    public void OnBeforeSerialize()
    {
        keys.Clear();
        values.Clear();

        foreach(KeyValuePair<TKey, TValue> kvp in this)
        {
            keys.Add(kvp.Key);
            values.Add(kvp.Value);
        }
    }
    public void OnAfterDeserialize()
    {
        this.Clear();


        if(keys.Count != values.Count)
        {
            Debug.Log("键数和值数不相等");//
        }   

        for (int i = 0; i < keys.Count; i++)
        {
            this.Add(keys[i], values[i]);
        }
    }

}

Inventory.cs

cs 复制代码
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

//放在创建仓库的empyty对象上,就是仓库的运行函数
public class Inventory : MonoBehaviour, ISaveManager//实现了ISaveManager接口
{
    public static Inventory instance;//单例模式

    public List<ItemData> startingItems;//初始装备


    //两种关键的存贮结构
    //List:存储玩家的装备、仓库物品、储藏室物品等
    //Dictionary:存储每个物品的数据以及物品在仓库中的具体信息,例如物品的堆叠数量
    public List<InventoryItem> equipment;
    public Dictionary<ItemData_Equipment, InventoryItem> equipmentDictionary;

    public List<InventoryItem> inventory;
    public Dictionary<ItemData, InventoryItem> inventoryDictionary;

    public List<InventoryItem> stash;
    public Dictionary<ItemData, InventoryItem> stashDictionary;


    //UI物品槽管理:通过以下代码将游戏中的物品槽和UI界面上的物品槽关联起来
    [Header("仓库UI")]//Inventory UI
    [SerializeField] private Transform inventorySlotParent;//位置
    [SerializeField] private Transform stashSlotParent;
    [SerializeField] private Transform equipmentSlotParent;
    [SerializeField] private Transform statSlotParent;


    //物品和材料的存贮位置分开

    private UI_ItemSlot[] inventoryItemSlot;
    private UI_ItemSlot[] stashItemSlot;//储藏室
    private UI_EquipmentSlot[] equipmentSlot;//装备
    private UI_StatSlot[] statSlot;


    [Header("物品冷却")]
    private float lastTimeUsedFlask;
    private float lastTimeUsedArmor;


    //P122解决一开始不能用物品的问题,因为一开始冷却被赋值,使用了currentFlask.itemCoolDown
    public float flaskCoolDown { get; private set; }
    private float armorCoolDown;


    [Header("数据库")]
    public List<InventoryItem> loadedItems;//加载的物品


    private void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);//防止从一个地方到另一个地方
    }


    private void Start()//初始实例化
    {
        inventory = new List<InventoryItem>();
        inventoryDictionary = new Dictionary<ItemData, InventoryItem>();

        stash = new List<InventoryItem>();
        stashDictionary = new Dictionary<ItemData, InventoryItem>();

        equipment = new List<InventoryItem>();
        equipmentDictionary = new Dictionary<ItemData_Equipment, InventoryItem>();


        //同时获取UI中对应的物品槽
        //获得起始的脚本
        inventoryItemSlot = inventorySlotParent.GetComponentsInChildren<UI_ItemSlot>();
        stashItemSlot = stashSlotParent.GetComponentsInChildren<UI_ItemSlot>();
        equipmentSlot = equipmentSlotParent.GetComponentsInChildren<UI_EquipmentSlot>();
        statSlot = statSlotParent.GetComponentsInChildren<UI_StatSlot>();
        AddStartingItems();

    }



    private void AddStartingItems()//添加初始物品
    {
        if(loadedItems.Count > 0)
        {
            foreach (InventoryItem item in loadedItems)
            {
                for (int i = 0; i < item.stackSize; i++)
                {
                    AddItem(item.data);
                }
            }

            return;
        }
        

        for (int i = 0; i < startingItems.Count; i++)
        {
            if (startingItems[i] != null)
                AddItem(startingItems[i]);
        }
    }


    public void EquipItem(ItemData _item)//把一个物品装备到角色身上
    {
        ItemData_Equipment newEquipment = _item as ItemData_Equipment;//把 _item 对象转换为 ItemData_Equipment 类型,表示它是一个装备物品

        InventoryItem newItem = new InventoryItem(newEquipment); //把 ItemData 对象转换为 InventoryItem 对象

        ItemData_Equipment oldEquipment = null;//要删除的物品


        foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary)//遍历装备字典
        {
            if (item.Key.equipmentType == newEquipment.equipmentType)//如果装备类型相同
                oldEquipment = item.Key;//删除该装备
        }


        if (oldEquipment != null)
        {
            UnequipItem(oldEquipment);
            AddItem(oldEquipment);//把要删除的物品放回仓库
        }

        equipment.Add(newItem);
        equipmentDictionary.Add(newEquipment, newItem);
        newEquipment.AddModifiers();//添加装备属性

        RemoveItem(_item);

        UpdataSlotsUI();
    }



    public void UnequipItem(ItemData_Equipment itemToRemove)//移除装备函数
    {
        if (equipmentDictionary.TryGetValue(itemToRemove, out InventoryItem value))
        {

            equipment.Remove(value);
            equipmentDictionary.Remove(itemToRemove);
            itemToRemove.RemoveModifiers();

        }
    }


    private void UpdataSlotsUI()//更新UI物体的数量
    {
        // 更新装备槽
        for (int i = 0; i < equipmentSlot.Length; i++)//将装备物品槽与一个装备字典中的物品进行匹配,并根据匹配结果更新物品槽的内容
        {

            foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary)//遍历装备字典
            {
                if (item.Key.equipmentType == equipmentSlot[i].slotType)//这个条件用于确保物品能放入正确的槽位中
                    equipmentSlot[i].UpdataSlot(item.Value);
            }
        }


        // 清空并更新仓库和储藏室的物品槽
        for (int i = 0; i < inventoryItemSlot.Length; i++)//仓库物品槽
        {
            inventoryItemSlot[i].CleanUpSlot();
        }
        for (int i = 0; i < stashItemSlot.Length; i++)//储藏室中的物品槽
        {
            stashItemSlot[i].CleanUpSlot();
        }


        // 重新填充仓库和储藏室
        for (int i = 0; i < inventory.Count; i++)
        {
            inventoryItemSlot[i].UpdataSlot(inventory[i]);
        }
        for (int i = 0; i < stash.Count; i++)
        {
            stashItemSlot[i].UpdataSlot(stash[i]);
        }

        UpdateStatsUI();
    }

    public void UpdateStatsUI()
    {
        //更新属性槽
        for (int i = 0; i < statSlot.Length; i++)
        {
            statSlot[i].UpdateStatValueUI();
        }
    }

    public void AddItem(ItemData _item)//据物品类型,将物品添加到仓库(inventory)或者储藏室(stash)
    {
        if (_item.itemType == ItemType.Equipment && CanAddItem())
        {
            AddToInventory(_item);
        }
        else if (_item.itemType == ItemType.Material)
        {
            AddToStash(_item);
        }

        UpdataSlotsUI();
    }

    private void AddToStash(ItemData _item)
    {
        if (stashDictionary.TryGetValue(_item, out InventoryItem value))
        {
            value.AddStack();
        }
        else
        {
            InventoryItem newItem = new InventoryItem(_item);
            stash.Add(newItem);
            stashDictionary.Add(_item, newItem);
        }
    }

    private void AddToInventory(ItemData _item)
    {
        if (inventoryDictionary.TryGetValue(_item, out InventoryItem value))//字典中检查仓库中是否有这个物品,具体查找的是ItemData,out InventoryItem value如果找,返回与该键相关联的值
        {
            value.AddStack();//如果物品已经存在于库存中,则增加其堆叠数量
        }
        else
        {
            InventoryItem newItem = new InventoryItem(_item);//如果物品不存在,则创建一个新的 InventoryItem
            inventory.Add(newItem);
            inventoryDictionary.Add(_item, newItem);
        }
    }//检查物品是否已经在仓库里,如果存在则增加堆叠数量,如果不存在则创建新的物品对象并加入仓库

    public void RemoveItem(ItemData _item)
    {
        if (inventoryDictionary.TryGetValue(_item, out InventoryItem value))
        {
            if (value.stackSize <= 1)
            {
                inventory.Remove(value);
                inventoryDictionary.Remove(_item);
            }
            else
            {
                value.RemoveStack();
            }
        }

        if (stashDictionary.TryGetValue(_item, out InventoryItem stashValue))
        {
            if (stashValue.stackSize <= 1)//如果物品的堆叠数量小于等于1,则从库存中删除该物品
            {
                stash.Remove(stashValue);
                stashDictionary.Remove(_item);
            }
            else
            {
                stashValue.RemoveStack();//否则就减少堆寨数量
            }
        }

        UpdataSlotsUI();
    }

    public bool CanAddItem()
    {
        if (inventory.Count >= inventoryItemSlot.Length)
        {
            //Debug.Log("仓库已满");
            return false;
        }
        return true;
    }


    public bool CanCraft(ItemData_Equipment _itemToCraft, List<InventoryItem> _requiredMaterials)//判断是否可以合成的函数
    {
        List<InventoryItem> materialsToRemove = new List<InventoryItem>();

        for (int i = 0; i < _requiredMaterials.Count; i++)
        {
            if (stashDictionary.TryGetValue(_requiredMaterials[i].data, out InventoryItem stashValue))//如果储藏室中没有所需材料
            {
                if (stashValue.stackSize < _requiredMaterials[i].stackSize)//数量是否足够
                {
                    Debug.Log("没有足够的材料");
                    return false;
                }
                else
                {
                    materialsToRemove.Add(stashValue);
                }
            }
            else
            {
                Debug.Log("没有足够的材料");
                return false;
            }
        }

        for (int i = 0; i < materialsToRemove.Count; i++)//使用了就从临时仓库中移除
        {
            RemoveItem(materialsToRemove[i].data);
        }

        AddItem(_itemToCraft);
        Debug.Log("这里是你的物品" + _itemToCraft.name);

        return true;

    }


    public List<InventoryItem> GetEquipmentList() => equipment;//获取装备列表

    public List<InventoryItem> GetStashList() => stash;//获取仓库列表


    public ItemData_Equipment GetEquipment(EquipmentType _type)//获取装备
    {
        ItemData_Equipment equipedItem = null;

        foreach (KeyValuePair<ItemData_Equipment, InventoryItem> item in equipmentDictionary)//遍历装备字典
        {
            if (item.Key.equipmentType == _type)//如果装备类型相同
                equipedItem = item.Key;//删除该装备
        }

        return equipedItem;
    }

    public void UseFlask()//使用药水的函数
    {
        ItemData_Equipment currentFlask = GetEquipment(EquipmentType.Flask);//获取当前的药水

        if (currentFlask == null)
            return;


        bool canUseFlask = Time.time > lastTimeUsedFlask + flaskCoolDown;//判断是否可以使用药水

        if (canUseFlask)
        {
            flaskCoolDown = currentFlask.itemCoolDown;//重置冷却时间,一开始就可以使用物品
            currentFlask.Effect(null);
            lastTimeUsedFlask = Time.time;
        }
        else
            Debug.Log("药水冷却中");
    }

    public bool CanUseArmor()
    {
        ItemData_Equipment currentArmor = GetEquipment(EquipmentType.Armor);//获取当前装备的护甲信息。

        if (Time.time > lastTimeUsedArmor + armorCoolDown)
        {
            //更新冷却时间和使用时间
            armorCoolDown = currentArmor.itemCoolDown;
            lastTimeUsedArmor = Time.time;

            return true;
        }

        Debug.Log("护甲正在冷却中");
        return false;

    }

    public void LoadData(GameData _data)//加载数据
    {
        foreach(KeyValuePair<string,int> pair in _data.inventory)
        {
            foreach(var item in GetItemDataBase())
            {
                if (item != null && item.itemId == pair.Key)
                {
                    InventoryItem itemToLoad = new InventoryItem(item);
                    itemToLoad.stackSize = pair.Value;

                    loadedItems.Add(itemToLoad);
                }
            }
        }
    }


    public void SaveData(ref GameData _data)
    {
        _data.inventory.Clear();

        foreach (KeyValuePair<ItemData, InventoryItem> pair in inventoryDictionary)
        {
            _data.inventory.Add(pair.Key.itemId, pair.Value.stackSize);
        }
    }


    private List<ItemData> GetItemDataBase()//获取物品数据库
    {
        List<ItemData> itemDatabase = new List<ItemData>();
        string[] assetNames = AssetDatabase.FindAssets("", new[] { "Assets/Data/Equipment" });

        foreach(string SOName in assetNames)
        {
            var SOpath = AssetDatabase.GUIDToAssetPath(SOName);
            var itemData = AssetDatabase.LoadAssetAtPath<ItemData>(SOpath);
            itemDatabase.Add(itemData);
        }

        return itemDatabase;

    }
}

ItemData.cs

cs 复制代码
using System.Text;
using UnityEditor;
using UnityEngine;
//2024年10月31日
//在敌人图层之上,可以看得到


public enum ItemType
{
    Material,
    Equipment
}


[CreateAssetMenu(fileName = "New Item Data", menuName = "Data/Item")]//在资源管理器中创建新的Item,创建的物品自带这个脚本

public class ItemData : ScriptableObject //在编辑器中创建和编辑的类
{
    public ItemType itemType;//定义你的物品的类型
    public string itemName;//定义你的物品的名字
    public Sprite icon;//可以选择图标
    public string itemId;//物品的ID

    [Range(0,100)]
    public float dropChance;//掉落几率

    protected StringBuilder sb = new StringBuilder();//创建一个字符串生成器

    private void OnValidate()
    {
#if UNITY_EDITOR//只在编辑器中运行
        string path = AssetDatabase.GetAssetPath(this);//获取这个物品的路径
        itemId = AssetDatabase.AssetPathToGUID(path);//获取这个物品的GUID
    }
#endif




    public virtual string GetDescription()//虚方法,返回一个字符串
    {
        return "";
    }

}

GameData.cs

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//2024.11.25
[System.Serializable]
public class GameData
{
    public int currency;

    public SerializableDictionary<string, int> inventory;//物品的名字和数量

    public GameData()
    {
        this.currency = 0;
        inventory = new SerializableDictionary<string, int>();
    }

}
相关推荐
jianchwa2 分钟前
3.10 内核 BUG_ON() at xfs_vm_writepage() -> page_buffers()
linux·运维·bug
NetX行者4 分钟前
Vue3+Typescript+Axios+.NetCore实现导出Excel文件功能
前端·typescript·c#·excel·.netcore
我的钱啊djlfakjsdlfj5 分钟前
crmeb 分享商品 邀请码 bug 修复 记录
bug
weixin_423995001 小时前
unity 绿幕抠图
unity·游戏引擎
xiandong202 小时前
241124_基于MindSpore学习GPT1
学习
吾与谁归in2 小时前
【C#设计模式(15)——命令模式(Command Pattern)】
设计模式·c#·命令模式
cwtlw2 小时前
数据库学习记录02
开发语言·数据库·笔记·sql·学习
MoFe12 小时前
【C#】串口数据传输
开发语言·c#
半夏知半秋3 小时前
unity中的Horizontal和Vertical介绍
笔记·学习·unity·c#·游戏引擎
Thomas_YXQ3 小时前
Unity3D ngui和ugui区别与优缺点详解
服务器·游戏·unity·unity3d·游戏开发