【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili
教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/
本章节实现了仓库物品的保存
SerializableDictionary.cs
Unity 中的序列化问题
- 标准字典无法序列化 :Unity 的序列化系统(如 Inspector 显示或存档功能)不支持
Dictionary<TKey, TValue>
,因为它无法直接保存键值对的关系。 - 解决方法 :通过两个列表(
keys
和values
)分别保存字典的键和值,再通过自定义逻辑将列表和字典互相转换。
序列化前处理 (OnBeforeSerialize
)
- 触发时机:当 Unity 序列化对象时(如保存场景或进入播放模式)。
- 作用 :
- 清空现有的
keys
和values
列表。 - 遍历字典中的所有键值对,将键存入
keys
列表,值存入values
列表。
- 清空现有的
- 结果:字典中的数据被转换为两个可序列化的列表。
反序列化后处理 (OnAfterDeserialize
)
- 触发时机:当 Unity 反序列化对象时(如加载场景或从存档中还原数据)。
- 作用 :
- 清空字典,确保它是空的。
- 检查
keys
和values
列表的长度是否一致。如果不一致,打印错误信息。 - 遍历
keys
和values
,将键值对逐一添加到字典中。
- 结果:列表中的数据被还原到字典中。
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>();
}
}