【Unity实战】最全面的库存系统(五)

文章目录

  • 先来看看最终效果
  • 前言
  • 配置商店系统数据
  • 创建另一个NPC
  • 绘制商店UI
  • 控制商店开关
  • 列出商品
  • 添加和删除物品功能
  • 添加商品到购物车
  • 购买商品
  • 购物车删除物品
  • 商店预览效果
  • 购买和出售切换
  • 出售功能
  • 保存商店数据
  • 快捷栏物品切换和使用
  • 完结

先来看看最终效果

在这里插入图片描述
在这里插入图片描述

前言

本期也是最好一期,紧跟着上期,继续来完善我们的库存系统,实现商店系统和快捷栏的切换功能

配置商店系统数据

新增ShopItemList ,配置商店物品列表信息

[CreateAssetMenu(menuName = "商店系统/商店物品列表")]
public class ShopItemList : ScriptableObject
{// 商店物品列表[SerializeField] private List<ShopInventoryItem> _items;// 商店允许的最大金币数[SerializeField] private int _maxAllowedGold;// 商店出售物品的加价比例[SerializeField] private float _sellMarkUp;// 商店购买物品的加价比例[SerializeField] private float _buyMarkUp;public List<ShopInventoryItem> Items => _items;public int MaxAllowedGold => _maxAllowedGold;public float SellMarkUp => _sellMarkUp;public float BuyMarkUp => _buyMarkUp;// 商店库存物品结构体[System.Serializable]public struct ShopInventoryItem{// 物品数据public InventoryItemData ItemData;// 物品数量public int Amount;}
}

新增ItemSlot,物品槽的抽象基类

public abstract class ItemSlot : ISerializationCallbackReceiver
{// 对数据的引用,使用[NonSerialized]属性表示在序列化时不包含该字段[NonSerialized] protected InventoryItemData itemData;// 用于序列化的字段[SerializeField] protected int _itemID = -1;[SerializeField] protected int stackSize;// 对外暴露的物品数据和堆叠大小属性public InventoryItemData ItemData => itemData;public int StackSize => stackSize;// 清空物品槽public void ClearSlot(){itemData = null;_itemID = -1;stackSize = 0;}// 分配一个物品给物品槽public void AssignItem(InventorySlot invSlot){if (itemData == invSlot.ItemData){AddToStack(invSlot.StackSize);}else{itemData = invSlot.ItemData;_itemID = itemData.ID;stackSize = 0;AddToStack(invSlot.StackSize);}}// 将当前背包的物品信息复制到克隆的背包系统中public void AssignItem(InventoryItemData data, int amount){if (itemData == data) AddToStack(amount);else{itemData = data;_itemID = data.ID;stackSize = 0;AddToStack(amount);}}// 将物品堆叠数量增加一定数量public void AddToStack(int amount){stackSize += amount;}// 从物品堆叠中移除一定数量的物品public void RemoveFromStack(int amount){stackSize -= amount;}// 在序列化之前的回调函数public void OnBeforeSerialize(){// 这里可以添加在序列化之前需要处理的逻辑}// 在反序列化之后的回调函数public void OnAfterDeserialize(){if (_itemID == -1) return;var db = Resources.Load<Database>("Database");itemData = db.GetItem(_itemID);} 
}

重构InventorySlot,继承前面的ItemSlot物品槽的抽象基类

//用于表示背包系统中的一个物品槽位
[System.Serializable]
public class InventorySlot : ItemSlot
{// 构造函数,用于创建一个带有物品和堆叠数量的槽public InventorySlot(InventoryItemData source, int amount){itemData = source;_itemID = itemData.ID;stackSize = amount;}// 默认构造函数,用于创建一个空槽public InventorySlot(){ClearSlot();}// 检查是否有足够的堆叠空间,并返回剩余的可堆叠数量public bool EnoughRoomLeftInStack(int amountToAdd, out int amountRemaining){amountRemaining = itemData.MaxStackSize - stackSize; // 计算剩余的可堆叠数量return EnoughRoomLeftInStack(amountToAdd);}// 检查是否有足够的堆叠空间public bool EnoughRoomLeftInStack(int amountToAdd){// 如果当前堆叠数量加上要添加的数量小于等于最大堆叠数量if (itemData == null || itemData != null && stackSize + amountToAdd <= itemData.MaxStackSize)return true;elsereturn false;}//更新库存槽的数据public void UpdateInventorySlot(InventoryItemData data, int amount){itemData = data;_itemID = itemData.ID;// 通过传入的物品数据获取物品IDstackSize = amount;}/// <summary>/// 将物品堆叠一分为二。/// </summary>/// <param name="splitStack">拆分后的新物品堆叠。</param>/// <returns>是否成功拆分。</returns>public bool SplitStack(out InventorySlot splitStack){// 如果物品堆叠为空或堆叠数量小于1,则无法拆分if (stackSize < 1){splitStack = null;return false;}// 计算需要拆分出的物品堆叠数量(约为原始堆叠数量的一半)int halfStack = Mathf.RoundToInt(stackSize / 2f);// 从原始堆叠中移除一部分物品RemoveFromStack(halfStack);// 创建一个新的物品堆叠作为拆分后的一半splitStack = new InventorySlot(itemData, halfStack);return true;}
}

新增ShopSlot,基础ItemSlot,对槽位进行了初始化操作

[System.Serializable]
public class ShopSlot: ItemSlot
{public ShopSlot(){ClearSlot();}
}

新增ShopSystem,定义了一个商店系统类,包含了商店的物品槽列表、金币数量、购买和出售的加价率等属性,以及相应的初始化和设置方法。这些属性和方法可以用于管理商店的状态和行为。

[System.Serializable]
public class ShopSystem
{// 商店的物品槽列表[SerializeField] private List<ShopSlot> _shopInventory;public List<ShopSlot> ShopInventory => _shopInventory;// 商店的可用金币数量[SerializeField] private int _availableGold;public int AvailableGold => _availableGold;// 商品的购买加价率和出售加价率[SerializeField] private float _buyMarkUp;public float BuyMarkUp => _buyMarkUp;[SerializeField] private float _sellMarkUp;public float S => _sellMarkUp;// 构造方法,初始化商店的大小、金币数量以及购买和出售的加价率public ShopSystem(int size, int gold, float buyMarkUp, float sellMarkUp){_availableGold = gold;_buyMarkUp = buyMarkUp;_sellMarkUp = sellMarkUp;SetShopSize(size);}// 设置商店的大小private void SetShopSize(int size){_shopInventory = new List<ShopSlot>(size);// 创建一个指定大小的物品槽列表for (int i = 0; i < size; i++){_shopInventory.Add(new ShopSlot());// 将新创建的物品槽添加到列表中}}// 向商店添加物品public void AddToShop(InventoryItemData data, int amount){if (ContainsItem(data, out ShopSlot shopSlot)){shopSlot.AddToStack(amount); // 如果商店已经存在相同的物品,则将物品堆叠数量增加}else{var freeSlot = GetFreeSlot(); // 获取一个空闲的物品槽freeSlot.AssignItem(data, amount); // 在空闲的物品槽中添加新的物品}}// 获取一个空闲的物品槽private ShopSlot GetFreeSlot(){var freeSlot = _shopInventory.FirstOrDefault(i => i.ItemData == null); // 查找第一个物品槽中没有物品的槽if (freeSlot == null){freeSlot = new ShopSlot(); // 如果没有空闲槽,则创建一个新的物品槽_shopInventory.Add(freeSlot); // 将新创建的物品槽添加到列表中}return freeSlot;}// 检查商店是否已经存在某个物品,并返回对应的物品槽public bool ContainsItem(InventoryItemData itemToAdd, out ShopSlot shopSlot){shopSlot = _shopInventory.Find(i => i.ItemData == itemToAdd); // 查找物品槽列表中是否存在相同的物品return shopSlot != null;}
}

新增ShopKeeper,定义了一个商店管理员类,该类包含了商店所持有的物品列表、商店系统以及与玩家交互的方法和事件。你需要根据具体需求来编写交互逻辑和结束交互的实现。

// 需要附加UniqueID组件方可使用
[RequireComponent(typeof(UniqueID))]
public class ShopKeeper : MonoBehaviour, IInteractable
{// 商店所持有的物品列表[SerializeField] private ShopItemList _shopItemsHeld;[SerializeField] private ShopSystem _shopSystem;public static UnityAction<ShopSystem, PlayerInventoryHolder> OnShopWindowRequested;private void Awake(){_shopSystem = new ShopSystem(_shopItemsHeld.Items.Count, _shopItemsHeld.MaxAllowedGold, _shopItemsHeld.BuyMarkUp, _shopItemsHeld.SellMarkUp);foreach (var item in _shopItemsHeld.Items){//打印测试Debug.Log($"{item.ItemData.DisplayName}:{item.Amount}");_shopSystem.AddToShop(item.ItemData, item.Amount);//向商店添加物品}}// 当交互完成时触发的事件public UnityAction<IInteractable> OnInteractionComplete { get; set; }// 当与玩家进行交互时调用public void Interact(Interactor interactor, out bool interactSuccessful){var playerInv = interactor.GetComponent<PlayerInventoryHolder>();if (playerInv != null){OnShopWindowRequested?.Invoke(_shopSystem, playerInv);interactSuccessful = true;}else{interactSuccessful = false;Debug.LogError("没找到PlayerInventoryHolder");}}// 结束与玩家的交互public void EndInteraction(){//}
}

运行查看是否还正常
在这里插入图片描述

设置物品价格
在这里插入图片描述
配置店铺数据,配置对应的价格比例,100的物品我们只能卖75,因为店铺要收25%的利润
在这里插入图片描述
添加商品库存
在这里插入图片描述
修改UniqueID,可以通过菜单初始化ID

[ContextMenu("生成ID")]
private void Generate()
{//。。。
}

配置NPC脚本,记得前面生成ID方法生成ID
在这里插入图片描述
打印
在这里插入图片描述
界面数据
在这里插入图片描述

创建另一个NPC

工具NPC
在这里插入图片描述
在这里插入图片描述

数据
在这里插入图片描述

绘制商店UI

绘制商店UI,这里我就不多介绍了,按自己喜欢绘制就行
在这里插入图片描述

控制商店开关

新增ShopKeeperDisplay 和ShoppingCartItemUI脚本

public class ShopKeeperDisplay : MonoBehaviour
{}
public class ShoppingCartItemUI: MonoBehaviour
{}

挂载脚本
在这里插入图片描述

在这里插入图片描述

模仿之前的InventoryUIController,新增UIController,控制商店窗口的显示隐藏

public class UIController : MonoBehaviour
{[SerializeField] private ShopKeeperDisplay _shopKeeperDisplay;private void Awake() {_shopKeeperDisplay.gameObject.SetActive(false);}void Update(){//activeInHierarchy 检查该对象是否处于活动状态if (_shopKeeperDisplay.gameObject.activeInHierarchy && Input.GetKeyDown(KeyCode.Escape)){// 按下 ESC 键关闭物品界面_shopKeeperDisplay.gameObject.SetActive(false);}}// 注册事件监听器,在脚本启用时调用private void OnEnable(){ShopKeeper.OnShopWindowRequested += DisplayShopWindow;}// 取消事件监听器,在脚本禁用时调用private void OnDisable(){ShopKeeper.OnShopWindowRequested -= DisplayShopWindow;}// 显示商店窗口的方法,响应ShopKeeper.OnShopWindowRequested事件private void DisplayShopWindow(ShopSystem shopSystem, PlayerInventoryHolder playerInventory){_shopKeeperDisplay.gameObject.SetActive(true);}
}

挂载脚本UI
在这里插入图片描述

新增NPC,添加配置,记得图层进行修改,因为前面我配置打开的图层是Box,所以这里偷懒也用Box,你也可以换成别的(可以Inventory图层比较通用)
在这里插入图片描述
效果,打开关闭商店
在这里插入图片描述

列出商品

修改ShopSlotUI ,控制商品槽UI

public class ShopSlotUI : MonoBehaviour
{[Header("商品图标组件")][SerializeField] private Image _itemSprite;[Header("商品名称的组件")][SerializeField] private TextMeshProUGUI _itemName; [Header("商品数量的组件")][SerializeField] private TextMeshProUGUI _itemCount;[Header("该UI对应的商店物品槽")][SerializeField] private ShopSlot _assignedItemSlot; public ShopSlot AssignedItemSlot => _assignedItemSlot;[Header("将商品添加到购物车的按钮")][SerializeField] private Button _addItemToCartButton;[Header("从购物车中移除商品的按钮")][SerializeField] private Button _removeItemFromCartButton;// 声明关联的商店窗口显示对象和价格调整比例public ShopKeeperDisplay ParentDisplay { get; private set; }public float Markup { get; private set; }//加价比例private void Awake(){// 初始化UI元素_itemSprite.sprite = null;_itemSprite.preserveAspect = true;_itemSprite.color = Color.clear;_itemName.text = "";_itemCount.text = "";// 添加按钮点击监听事件_addItemToCartButton?.onClick.AddListener(AddItemToCart);_removeItemFromCartButton?.onClick.AddListener(RemoveItemFromCart);// 获取关联的商店窗口显示对象ParentDisplay = transform.parent.GetComponentInParent<ShopKeeperDisplay>();}// 初始化商店物品槽UIpublic void Init(ShopSlot slot, float markup){_assignedItemSlot = slot;Markup = markup;UpdateUISlot();}// 更新商店物品槽UI的显示private void UpdateUISlot(){if (_assignedItemSlot.ItemData != null){// 显示物品图标、名称、数量和价格_itemSprite.sprite = _assignedItemSlot.ItemData.Icon;_itemSprite.color = Color.white;_itemCount.text = _assignedItemSlot.StackSize.ToString();_itemName.text = $"{_assignedItemSlot.ItemData.DisplayName} - {_assignedItemSlot.ItemData.GoldValue}G";}else{// 如果物品为空,则清空显示_itemSprite.sprite = null;_itemSprite.color = Color.clear;_itemName.text = "";_itemCount.text = "";}}// 从购物车中移除物品private void RemoveItemFromCart(){Debug.Log("从购物车中移除物品");}// 将物品添加到购物车中private void AddItemToCart(){Debug.Log("向购物车中添加商品");}
}

挂载脚本,配置参数
在这里插入图片描述

修改ShopKeeperDisplay

public class ShopKeeperDisplay : MonoBehaviour
{[Header("商店槽预制体")][SerializeField] private ShopSlotUI _shopSlotPrefab;[Header("购物车物品预制体")][SerializeField] private ShoppingCartItemUI _shoppingCartItemPrefab;[Header("购买标签按钮")][SerializeField] private Button _buyTabButton;[Header("出售标签按钮")][SerializeField] private Button _sellTabButton;[Space][Header("购物车")][Header("购物车总价文本")][SerializeField] private TextMeshProUGUI _basketTotalText;[Header("玩家金币数文本")][SerializeField] private TextMeshProUGUI _playerGoldText;[Header("商店金币数文本")][SerializeField] private TextMeshProUGUI _shopGoldText;[Header("购买按钮")][SerializeField] private Button _buyButton;[Header("购买按钮文本")][SerializeField] private TextMeshProUGUI _buyButtonText;[Space][Header("物品预览区域")][Header("物品预览图像")][SerializeField] private Image _itemPreviewSprite;[Header("物品名称文本")][SerializeField] private TextMeshProUGUI _itemPreviewName;[Header("物品描述文本")][SerializeField] private TextMeshProUGUI _itemPreviewDescription;[Header("物品列表内容面板")][SerializeField] private GameObject _itemListContentPanel;[Header("购物车物品列表内容面板")][SerializeField] private GameObject _shoppingCartContentPanel;private int _basketTotal;// 购物车总价格private ShopSystem _shopSystem;// 商店系统组件private PlayerInventoryHolder _playerInventoryHolder;private Dictionary<InventoryItemData, int> _shoppingCart = new Dictionary<InventoryItemData, int>();// 购物车字典private Dictionary<InventoryItemData, ShoppingCartItemUI> _shoppingCartUI = new Dictionary<InventoryItemData, ShoppingCartItemUI>();// 显示购物车物品信息的字典public void DisplayShopWindow(ShopSystem shopSystem, PlayerInventoryHolder playerInventoryHolder){_shopSystem = shopSystem;_playerInventoryHolder = playerInventoryHolder;RefreshDisplay();}private void RefreshDisplay(){ClearSlots();_basketTotalText.enabled = false;_buyButton.gameObject.SetActive(false);_basketTotal = 0;_playerGoldText.text = $"PLayer Gold:{_playerInventoryHolder.PrimaryInventorySystem.Gold}";_shopGoldText.text = $"Shop Gold:{_shopSystem.AvailableGold}";DisplayShopInventory();}private void ClearSlots(){_shoppingCart = new Dictionary<InventoryItemData, int>();_shoppingCartUI = new Dictionary<InventoryItemData, ShoppingCartItemUI>();// 清空物品列表内容面板和购物车物品列表内容面板中的所有子物体foreach (var item in _itemListContentPanel.transform.Cast<Transform>()){Destroy(item.gameObject);}foreach (var item in _shoppingCartContentPanel.transform.Cast<Transform>()){Destroy(item.gameObject);}}private void DisplayShopInventory(){// 遍历商店物品列表,显示每个物品槽foreach (var item in _shopSystem.ShopInventory){if (item.ItemData == null) continue;var shopSlot = Instantiate(_shopSlotPrefab, _itemListContentPanel.transform);shopSlot.Init(item, _shopSystem.BuyMarkUp);}}
}

修改InventorySystem

[SerializeField] private int _gold;
public int Gold => _gold;//用于创建一个具有指定大小的背包系统
public InventorySystem(int size)
{_gold = 0;CreateInventory(size);
}
public InventorySystem(int size, int gold)
{_gold = gold;CreateInventory(size);
}private void CreateInventory(int size)
{//根据指定的大小创建对应数量的空物品槽位inventorySlots = new List<InventorySlot>(size);for (int i = 0; i < size; i++){inventorySlots.Add(new InventorySlot());}
}

修改UIController

//显示商店窗口的方法,响应ShopKeeper.OnShopWindowRequested事件
private void DisplayShopWindow(ShopSystem shopSystem, PlayerInventoryHolder playerInventory)
{_shopKeeperDisplay.gameObject.SetActive(true);_shopKeeperDisplay.DisplayShopWindow(shopSystem, playerInventory);
}

挂载脚本,配置参数
在这里插入图片描述

效果
在这里插入图片描述

添加和删除物品功能

修改ShopSlotUI

// 记录临时数量,初始化为物品槽的堆叠数量
private int _tempAmount;public void Init(ShopSlot slot, float markup)
{_assignedItemSlot = slot;Markup = markup;_tempAmount = slot.StackSize;// 将临时数量初始化为物品槽的堆叠数量UpdateUISlot();
}// 从购物车中移除物品
private void RemoveItemFromCart()
{Debug.Log("从购物车中移除物品");// 如果临时数量等于物品槽堆叠数量,则不做任何操作,直接返回if (_tempAmount ==_assignedItemSlot.StackSize)return;_tempAmount++;ParentDisplay.RemoveItemFromCart(this);// 从购物车中移除该物品槽对应的物品_itemCount.text =_tempAmount.ToString();// 更新物品数量的文本显示
}// 将物品添加到购物车中
private void AddItemToCart()
{Debug.Log("向购物车中添加商品");// 如果临时数量小于等于 0,则不做任何操作,直接返回if (_tempAmount <= 0) return;_tempAmount--;ParentDisplay.AddItemToCart(this);// 将该物品槽对应的物品添加到购物车中_itemCount.text = _tempAmount.ToString();// 更新物品数量的文本显示
}

修改ShopKeeperDisplay

// 从购物车中移除该物品槽对应的物品
public void RemoveItemFromCart(ShopSlotUI shopSlotUI)
{//
}
// 将该物品槽对应的物品添加到购物车中
public void AddItemToCart(ShopSlotUI shopSlotUI)
{//
}

效果
在这里插入图片描述

添加商品到购物车

修改ShoppingCartItemUI,控制购物车插槽的UI

public class ShoppingCartItemUI : MonoBehaviour
{[SerializeField] private TextMeshProUGUI _itemText;//设置物品信息文本public void SetItemText(string newString){_itemText.text = newString;}
}

挂载脚本
在这里插入图片描述
修改ShopKeeperDisplay

private bool _isSelling;// 是否为出售模式// 将该物品槽对应的物品添加到购物车中
public void AddItemToCart(ShopSlotUI shopSlotUI)
{// 获取物品数据var data = shopSlotUI.AssignedItemSlot.ItemData;// 更新物品预览UpdateItemPreview(shopSlotUI);var price = GetModifiedPrice(data, 1, shopSlotUI.MarkUp);// 获取修改后的价格if (_shoppingCart.ContainsKey(data)){_shoppingCart[data]++;var newString = $"{data.DisplayName} ({price}G)x{_shoppingCart[data]}";// 如果购物车中已经有该物品,增加其数量_shoppingCartUI[data].SetItemText(newString);// 更新显示购物车物品信息的文本组件}else{_shoppingCart.Add(data, 1);// 否则,在购物车中添加该物品var shoppingCartTextObj = Instantiate(_shoppingCartItemPrefab, _shoppingCartContentPanel.transform);// 创建一个新的显示购物车物品信息的文本组件var newString = $"{data.DisplayName} ({price}G)x1";shoppingCartTextObj.SetItemText(newString);_shoppingCartUI.Add(data, shoppingCartTextObj); // 添加到显示购物车物品信息的字典中}_basketTotal += price;_basketTotalText.text = $"Total:{_basketTotal}G";// 更新购物车总价格的文本显示if (_basketTotal > 0 && !_basketTotalText.IsActive()){_basketTotalText.enabled = true;// 如果购物车总价大于 0 且文本组件是激活状态,启用文本组件_buyButton.gameObject.SetActive(true);// 激活“购买”按钮}CheckCartVsAvailableGold();
}// 更新物品预览
private void UpdateItemPreview(ShopSlotUI shopSlotUI) {}/// <summary>
/// 获取修改后的价格
/// </summary>
/// <param name="data">物品数据</param>
/// <param name="amount">物品数量</param>
/// <param name="markUp">加价比例</param>
/// <returns>修改后的价格</returns>
private static int GetModifiedPrice(InventoryItemData data, int amount, float markUp)
{// 计算基础价格,即物品单价乘以数量var baseValue = data.GoldValue * amount;// 计算调整后的价格,即基础价格加上涨价比例所得到的价格, 结果向下取整return Mathf.FloorToInt(baseValue + baseValue * markUp);
}//检查购物车与可用金币数之间的比较
private void CheckCartVsAvailableGold()
{// 如果是出售模式,获取商店可用金币数,否则获取玩家背包的金币数var goldToCheck = _isSelling ? _shopSystem.AvailableGold : _playerInventoryHolder.PrimaryInventorySystem.Gold;// 如果购物车总价大于可用金币数,将文本颜色设置为红色,否则为白色_basketTotalText.color = _basketTotal > goldToCheck ? Color.red : Color.white;// 如果是出售模式或者玩家背包还有足够的空间,则直接返回if (_isSelling || _playerInventoryHolder.PrimaryInventorySystem.CheckInventoryRemaining(_shoppingCart)) return;//库存不足_basketTotalText.text = "Insufficient inventory";_basketTotalText.color = Color.red;
}

修改InventorySystem

/// <summary>
/// 检查背包剩余空间是否足够容纳购物车中的物品
/// </summary>
/// <param name="shoppingCart">购物车字典,键为物品数据,值为物品数量</param>
/// <returns>如果背包剩余空间足够容纳购物车中的物品,则返回true;否则返回false</returns>
public bool CheckInventoryRemaining(Dictionary<InventoryItemData, int> shoppingCart)
{var clonedSystem = new InventorySystem(InventorySize);// 克隆一个背包系统for (int i = 0; i < InventorySize; i++){// 将当前背包的物品信息复制到克隆的背包系统中clonedSystem.InventorySlots[i].AssignItem(InventorySlots[i].ItemData, InventorySlots[i].StackSize);}foreach (var kvp in shoppingCart)// 遍历购物车中的物品{for (int i = 0; i < kvp.Value; i++)// 遍历购物车中每种物品的数量{// 尝试将物品添加到克隆的背包系统中,如果添加失败(即背包空间不足),则返回falseif (!clonedSystem.AddToInventory(kvp.Key, 1)) return false;}}// 所有物品都能成功添加到背包中,则返回truereturn true;
}

效果,添加物品,购物车自动计算价格,如果玩家金币不足则会显示红色
在这里插入图片描述

购买商品

修改ShopSlotUI

// 更新商店物品槽UI的显示
private void UpdateUISlot()
{if (_assignedItemSlot.ItemData != null){// 。。。// 获取调整后的物品价格 var modifiedPrice = ShopKeeperDisplay.GetModifiedPrice(_assignedItemSlot.ItemData, 1, MarkUp);// 显示物品名称和价格_itemName.text = $"{_assignedItemSlot.ItemData.DisplayName} - {modifiedPrice}G";}else{// 。。。}
}

修改ShopKeeperDisplay

private void RefreshDisplay()
{if (_buyButton != null){// 设置购买按钮的文字和点击事件_buyButtonText.text = _isSelling ? "Sell":"Buy";_buyButton.onClick.RemoveAllListeners();if (_isSelling) _buyButton.onClick.AddListener(SellItems);else _buyButton.onClick.AddListener(BuyItems);}//。。。
}//出售点击事件
private void SellItems()
{//
}//购买点击事件
private void BuyItems()
{// 检查玩家是否有足够的金币购买所有物品if (_playerInventoryHolder.PrimaryInventorySystem.Gold < _basketTotal){Debug.Log("玩家金币不足");return;}// 检查玩家背包是否有足够的空间存放购物车中的物品if (!_playerInventoryHolder.PrimaryInventorySystem.CheckInventoryRemaining(_shoppingCart)){Debug.Log("玩家背包位置不足");return;}// 逐个购买购物车中的物品并添加到玩家背包中foreach (var kvp in _shoppingCart){_shopSystem.PurchaseItem(kvp.Key, kvp.Value);for (int i = 0; i < kvp.Value; i++){_playerInventoryHolder.PrimaryInventorySystem.AddToInventory(kvp.Key, 1);}}// 商店获得金币,玩家失去金币_shopSystem.GainGold(_basketTotal);_playerInventoryHolder.PrimaryInventorySystem.SpendGold(_basketTotal);// 刷新商店界面显示RefreshDisplay();
}

修改ShopSystem

public void PurchaseItem(InventoryItemData data, int amount)
{// 检查商店是否包含这种物品,如果不包含,直接返回if (!ContainsItem(data, out ShopSlot slot)) return;// 从物品槽中移除指定数量的物品slot.RemoveFromStack(amount);
}public void GainGold(int basketTotal)
{// 将购物车总额添加到可用金币中_availableGold += basketTotal;
}

修改InventorySystem

public void SpendGold(int basketTotal)
{// 从玩家金币中扣除购物车总额_gold -= basketTotal;
}

修改InventoryHolder

[SerializeField] protected int _gold;//玩家金币量protected virtual void Awake()
{SaveLoad.OnLoadGame += LoadInventory;// 注册加载游戏事件//创建一个具有指定大小的背包系统inventorySystem = new InventorySystem(inventorySize, _gold);
}

为了测试,先给玩家一些钱
在这里插入图片描述
效果,购买后玩家金额减少,商店金额增加
在这里插入图片描述

购物车删除物品

修改ShopKeeperDisplay,完善购物车删除物品事件

// 从购物车中移除该物品槽对应的物品
public void RemoveItemFromCart(ShopSlotUI shopSlotUI)
{var data = shopSlotUI.AssignedItemSlot.ItemData;var price = GetModifiedPrice(data, 1, shopSlotUI.MarkUp);// 获取物品的价格if (_shoppingCart.ContainsKey(data))// 如果购物车中存在该物品{_shoppingCart[data]--;// 将物品数量减1// 更新该物品UI的显示var newString = $"{data.DisplayName}({price}G) x{_shoppingCart[data]}";_shoppingCartUI[data].SetItemText(newString);// 如果移除该物品后,该物品数量为0if (_shoppingCart[data] <= 0){// 从购物车和UI字典中移除该物品_shoppingCart.Remove(data);var tempobj =_shoppingCartUI[data].gameObject;// 获取该物品UI对象_shoppingCartUI.Remove(data);Destroy(tempobj);// 销毁该物品UI对象}}_basketTotal -= price; // 减去该物品的价格_basketTotalText.text = $"Total:{_basketTotal}G";// 更新购物车总价UI的文本显示// 如果购物车总价小于等于0且购物车总价UI处于激活状态if (_basketTotal <= 0 && _basketTotalText.IsActive()){_basketTotalText.enabled = false;// 关闭购物车总价UI的显示_buyButton.gameObject.SetActive(false);// 隐藏购买按钮ClearItemPreview();// 清空物品预览return;}// 检查购物车总价和玩家拥有的金币数是否匹配CheckCartVsAvailableGold();
}// 清空物品预览
private void ClearItemPreview()
{//
}

效果
在这里插入图片描述

商店预览效果

修改ShopKeeperDisplay

private void RefreshDisplay()
{//...ClearItemPreview();// 清空物品预览if (_isSelling) DisplayPlayerInventory(); // 如果是卖家,显示玩家的库存else DisplayShopInventory();// 否则显示商店的库存  
}//显示玩家的库存
private void DisplayPlayerInventory()
{}// 清空物品预览
private void ClearItemPreview()
{_itemPreviewSprite.sprite = null;_itemPreviewSprite.color = Color.clear;_itemPreviewName.text = "";_itemPreviewDescription.text = "";
}// 更新物品预览
private void UpdateItemPreview(ShopSlotUI shopSlotUI) { var data = shopSlotUI.AssignedItemSlot.ItemData;_itemPreviewSprite.sprite = data.Icon;_itemPreviewSprite.color = Color.white;_itemPreviewName.text = data.DisplayName;_itemPreviewDescription.text = data.Description;
}

效果
在这里插入图片描述

购买和出售切换

修改ShopKeeperDisplay,完善显示玩家的库存事件,并添加切换事件

//显示玩家的库存
private void DisplayPlayerInventory()
{foreach (var item in _playerInventoryHolder.PrimaryInventorySystem.GetAllItemsHeld()){var tempSlot = new ShopSlot();tempSlot.AssignItem(item.Key,item.Value);// 分配物品var shopSlot = Instantiate(_shopSlotPrefab,_itemListContentPanel.transform);shopSlot.Init(tempSlot, _shopSystem.SellMarkUp);// 初始化槽位}
}public void OnBuyTabPressed()
{_isSelling = false;RefreshDisplay();
}
public void OnSellTabPressed()
{_isSelling = true;RefreshDisplay();
}

修改InventorySystem

//获取玩家库存中所有持有的物品及其数量的方法
public Dictionary<InventoryItemData, int> GetAllItemsHeld()
{var distinctItems = new Dictionary<InventoryItemData, int>();foreach (var item in inventorySlots){if (item.ItemData == null) continue;// 如果物品数据为空,跳过当前循环if (!distinctItems.ContainsKey(item.ItemData)) distinctItems.Add(item.ItemData, item.StackSize);// 如果字典中不存在该物品数据,则添加到字典中else distinctItems[item.ItemData] += item.StackSize; // 否则,增加物品数量}return distinctItems;
}

绑定点击事件
在这里插入图片描述
在这里插入图片描述
效果
在这里插入图片描述

出售功能

修改ShopKeeperDisplay

// 出售点击事件
private void SellItems()
{// 如果商店可用金币少于购物车中物品的总价值,则无法出售if (_shopSystem.AvailableGold < _basketTotal)return;// 遍历购物车中的物品foreach (var kvp in _shoppingCart){// 获取物品的调整价格var price = GetModifiedPrice(kvp.Key, kvp.Value, _shopSystem.SellMarkUp);// 在商店中出售物品,并减少商店金额_shopSystem.SellItem(kvp.Key, kvp.Value, price);// 玩家增加金币_playerInventoryHolder.PrimaryInventorySystem.GainGold(price);// 从玩家库存系统中移除出售的物品_playerInventoryHolder.PrimaryInventorySystem.RemoveItemsFromInventory(kvp.Key, kvp.Value);}// 刷新显示RefreshDisplay();
}

修改ShopSystem

//出售物品
public void SellItem(InventoryItemData itemData, int quantity, int price)
{// 将物品添加到商店中AddToShop(itemData, quantity);// 减少商店金额ReduceGold(price);
}
private void ReduceGold(int price){// 减少商店金额_availableGold -= price;
}

修改InventorySystem

// 玩家增加金币
public void GainGold(int price)
{_gold += price;
}//从库存中移除物品
public void RemoveItemsFromInventory(InventoryItemData data, int amount)
{// 检查库存中是否包含指定物品,并获取包含该物品的所有库存槽if (ContainsItem(data, out List<InventorySlot> inventorySlots)){foreach (var slot in inventorySlots){var stackSize = slot.StackSize;if (stackSize > amount){// 如果库存槽中的堆叠大小 大于 要移除的数量,则从堆叠中移除指定数量的物品slot.RemoveFromStack(amount);// 触发库存槽变化事件OnInventorySlotChanged?.Invoke(slot);return;}else{// 否则则从堆叠中移除全部物品,并更新剩余数量slot.RemoveFromStack(stackSize);amount -= stackSize;// 触发库存槽变化事件OnInventorySlotChanged?.Invoke(slot);}}}
}

修改ItemSlot

// 从物品堆叠中移除一定数量的物品
public void RemoveFromStack(int amount)
{stackSize -= amount;// 如果堆叠大小小于等于0,则清空槽位if(stackSize <= 0) ClearSlot();
}

效果
在这里插入图片描述

保存商店数据

修改ShopKeeper

private string _id;// 商店管理员的唯一标识符
private ShopSaveData _shopSaveData;// 商店保存数据private void Awake(){//。。。_id= GetComponent<UniqueID>().ID;// 获取商店管理员的唯一标识符_shopSaveData = new ShopSaveData(_shopSystem);// 创建商店保存数据对象
}private void Start()
{// 如果存档管理器中不包含该商店管理员的数据,则将其添加到存档管理器中if (!SaveGameManager.data._shopKeeperDictionary.ContainsKey(_id))SaveGameManager.data._shopKeeperDictionary.Add(_id, _shopSaveData);
}
private void OnEnable()
{// 注册加载游戏事件时,调用加载物品方法SaveLoad.OnLoadGame += LoadInventory;
}
private void OnDisable()
{// 取消注册加载游戏事件时,调用加载物品方法SaveLoad.OnLoadGame -= LoadInventory;
}
private void LoadInventory(SaveData data)
{// 如果存档中不包含该商店管理员的数据,则直接返回if (!data._shopKeeperDictionary.TryGetValue(_id, out ShopSaveData shopSaveData)) return;// 将存档中的商店保存数据赋值给当前商店管理员_shopSaveData = shopSaveData;// 更新商店系统_shopSystem = _shopSaveData.ShopSystem;
}[System.Serializable]
public class ShopSaveData
{public ShopSystem ShopSystem;// 商店系统public ShopSaveData(ShopSystem shopSystem){ShopSystem = shopSystem;}
}

修改SaveData

//用于保存商店的数据
public SerializableDictionary<string, ShopSaveData> _shopKeeperDictionary;_shopKeeperDictionary = new SerializableDictionary<string, ShopSaveData>();

效果,我先购买了5个木头保存,在重新运行游戏,加载商店数据
在这里插入图片描述

快捷栏物品切换和使用

修改InventorySlot_UI,实现切换快捷栏颜色

private Image image;
//选中和不选中颜色
public Color selectedColor, notSelectedColor;private void Awake()
{//。。。image = GetComponent<Image>();Deselect();// 初始化时取消选中
}//选择该槽位颜色修改
public void Select()
{image.color = selectedColor;
}//取消选择该槽位颜色修改
public void Deselect()
{image.color = notSelectedColor;
}

配置参数
在这里插入图片描述
修改StaticInventoryDisplay

int selectedSlot = -1;//快捷栏索引
private Dictionary<KeyCode, int> slotIndexMap = new Dictionary<KeyCode, int>()
{{ KeyCode.Alpha1, 0 },{ KeyCode.Alpha2, 1 },{ KeyCode.Alpha3, 2 },{ KeyCode.Alpha4, 3 },{ KeyCode.Alpha5, 4 },{ KeyCode.Alpha6, 5 },{ KeyCode.Alpha7, 6 },{ KeyCode.Alpha8, 7 },{ KeyCode.Alpha9, 8 },{ KeyCode.Alpha0, 9 }
};protected override void Start()
{//。。。ChangeSelectedSlot(0);//默认选中第一个槽
}
private void Update()
{//快捷栏数字切换foreach (var kvp in slotIndexMap){if (Input.GetKeyDown(kvp.Key)){ChangeSelectedSlot(kvp.Value);break;}}//使用物品测试if (Input.GetKeyDown(KeyCode.Space)){InventorySlot_UI slot = GetSelectedItem();if (slot && slot.AssignedInventorySlot.ItemData){Debug.Log("使用了物品:" + slot.AssignedInventorySlot.ItemData.DisplayName);// 从物品堆叠中移除一定数量的物品slot.AssignedInventorySlot.RemoveFromStack(1);slot.UpdateUISlot();// 更新物品槽的显示}else{Debug.Log("没有物品可是使用!");}}
}//切换快捷栏选择
void ChangeSelectedSlot(int newValue)
{if (selectedSlot >= 0){slots[selectedSlot].Deselect();// 取消之前选中的槽位}slots[newValue].Select();// 选择新的槽位selectedSlot = newValue;// 更新选中的槽位索引
}// 获取当前选中快捷栏信息
public InventorySlot_UI GetSelectedItem()
{if (slots.Length > 0){InventorySlot_UI slot = slots[selectedSlot];// 获取当前选中槽位if (slot != null) return slot;// 如果有物品,则返回物品}return null;//如果没有选中物品则返回null
}

效果
在这里插入图片描述

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。点赞越多,更新越快哦!当然,如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇,https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/180387.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

直播间讨论区需要WebSocket,简单了解下

由于 http 存在一个明显的弊端&#xff08;消息只能有客户端推送到服务器端&#xff0c;而服务器端不能主动推送到客户端&#xff09;&#xff0c;导致如果服务器如果有连续的变化&#xff0c;这时只能使用轮询&#xff0c;而轮询效率过低&#xff0c;并不适合。于是 WebSocket…

Git 案例(企业如何使用git开发项目)

一、企业中我们是如何开发 1) 入职第一天,管理人员分配/git账号密码 2) 开发人员下载代码即文档/ 根据文档将环境搭建成功 3) 团队一般会给你讲讲项目相关的支持 4) 你接到第一个需求(或者某个功能,一般要经过沟通,分析,设计...等过程) 5) 创建feature分支(一般一个需求对应…

FPGA高端项目:图像采集+GTP+UDP架构,高速接口以太网视频传输,提供2套工程源码加QT上位机源码和技术支持

目录 1、前言免责声明本项目特点 2、相关方案推荐我这里已有的 GT 高速接口解决方案我这里已有的以太网方案 3、设计思路框架设计框图视频源选择OV5640摄像头配置及采集动态彩条视频数据组包GTP 全网最细解读GTP 基本结构GTP 发送和接收处理流程GTP 的参考时钟GTP 发送接口GTP …

【PyQt学习篇 · ⑪】:QPushButton和QCommandLinkButton的使用

文章目录 构造函数菜单设置扁平化默认处理右键菜单QCommandLinkButton的使用 构造函数 QPushButton的构造函数如下&#xff1a; """QPushButton(parent: Optional[QWidget] None)QPushButton(text: Optional[str], parent: Optional[QWidget] None)QPushButt…

自定义的卷积神经网络模型CNN,对图片进行分类并使用图片进行测试模型-适合入门,从模型到训练再到测试,开源项目

自定义的卷积神经网络模型CNN&#xff0c;对图片进行分类并使用图片进行测试模型-适合入门&#xff0c;从模型到训练再到测试&#xff1a;开源项目 开源项目完整代码及基础教程&#xff1a; https://mbd.pub/o/bread/ZZWclp5x CNN模型&#xff1a; 1.导入必要的库和模块&…

进阶C语言-指针的进阶(三)

模拟实现qsort函数 &#x1f388;1.测试bubble_sort&#xff0c;排序整型数组&#x1f388;2测试bubble_sort&#xff0c;排序结构体数组 &#x1f4dd;关于qsort函数&#xff0c;我们可以先去cpluplus网站上面了解一下&#xff1a; //1.排序整型数组&#xff0c;两个整型可以…

玻色量子“天工量子大脑”亮相中关村论坛,大放异彩

2023年5月25日至30日&#xff0c;2023中关村论坛&#xff08;科博会&#xff09;在北京盛大召开。中关村论坛&#xff08;科博会&#xff09;是面向全球科技创新交流合作的国家级平台行业盛会&#xff0c;由科技部、国家发展改革委、工业和信息化部、国务院国资委、中国科学院、…

使用Selenium IDE录制脚本

今天&#xff0c;我们开始介绍基于开源Selenium工具的Web网站自动化测试。 Selenium包含了3大组件&#xff0c;分别为&#xff1a;1. Selenium IDE 基于Chrome和Firefox扩展的集成开发环境&#xff0c;可以录制、回放和导出不同语言的测试脚本。 2. WebDriver 包括一组为不同…

项目实战:展示第一页数据

1、在FruitDao接口中添加查询第一页数据和查询总记录条数 package com.csdn.fruit.dao; import com.csdn.fruit.pojo.Fruit; import java.util.List; //dao &#xff1a;Data Access Object 数据访问对象 //接口设计 public interface FruitDao {void addFruit(Fruit fruit);vo…

Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍

目录 一. Redis简介 二. Redis的安装 2.1 Linux版安装 2.2 windows版安装 三. Redis的常用命令 一. Redis简介 Redis是一个开源&#xff08;BSD许可&#xff09;&#xff0c;内存存储的数据结构服务器&#xff0c;可用作数据库&#xff0c;高速缓存和消息队列代理。 它…

【ES专题】ElasticSearch搜索进阶

目录 前言阅读导航前置知识特别提醒笔记正文一、分词器详解1.1 基本概念1.2 分词发生的时期1.3 分词器的组成1.3.1 切词器&#xff1a;Tokenizer1.3.2 词项过滤器&#xff1a;Token Filter1.3.3 字符过滤器&#xff1a;Character Filter 1.4 倒排索引的数据结构 <font color…

管理文件:文件批量重命名,轻松删除文件名中的空格

在文件管理中&#xff0c;我们经常会遇到文件名中带有空格的情况。这些空格可能会使文件在某些情况下难以被正确识别或使用&#xff0c;因此我们需要掌握一些技巧来轻松删除文件名中的空格。现在使用云炫文件管理器批量重命名进行批量处理。以下是如何操作的步骤详解&#xff1…

网易按照作者批量采集新闻资讯软件说明文档

大家好&#xff0c;我是淘小白~ 今天给大家介绍的爬虫软件是网易按照作者采集的软件 1、软件语言&#xff1a; Python 2、使用到的工具 Python selenium库、谷歌浏览器、谷歌浏览器驱动 3、文件说明&#xff1a; 4、配置文件说明&#xff1a; 5、环境配置 安装Python&am…

基于8051单片机与1601LCD的计算器设计

**单片机设计介绍&#xff0c;1665基于8051单片机与1601LCD的计算器设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于8051单片机和1601LCD的计算器设计是一种简单的嵌入式系统应用&#xff0c;其设计过程包括以下几个步骤…

联通智网科技正式入选国家级专精特新“小巨人”企业

近日&#xff0c;北京市经济和信息化局发布《第五批专精特新“小巨人”企业公告名单》&#xff0c;根据工业和信息化部发布的《工业和信息化部关于公布第五批专精特新“小巨人”企业和通过复核的第二批专精特新“小巨人”企业名单的通告》&#xff0c;联通智网科技股份有限公司…

百度竞价排名推广对比自然排名哪一个更具优势-华媒舍

在搜索引擎结论网页页面&#xff08;SERP&#xff09;中&#xff0c;我们经常会看到一些网站链接及其广告栏。这种连接一般分为两种类型&#xff1a;百度竞价推广排名推广与自然排名。究竟哪个更有优势&#xff1f;本文将对这几种排名形式进行科谱详细介绍。 什么叫百度竞价推广…

产品经理入门学习(三):产品解决方案

参考引用 黑马-产品经理入门基础课程 1. 需求分析 1.1 需求分析的目的 1.2 需求分析的方法 案例分析 福特公司的创始人亨利福特说&#xff1a;如果我当年去问顾客他们想要什么&#xff0c;他们肯定会告诉我&#xff1a;一匹更快的马 1.3 需求分析的实际应用 人性七宗罪&#…

javassmmysql爱心捐赠物资维护系统09536-计算机毕业设计项目选题推荐(附源码)

摘要 随着信息技术的快速发展&#xff0c;计算机应用已经进入成千上万的家庭。随着物资数量的增加&#xff0c;物资库存管理也存在许多问题。物资数据的处理量正在迅速增加&#xff0c;原来的手工管理模式不适合这种形式。使用计算机可以完成数据收集、处理和分析&#xff0c;减…

SSM校园设备管信息管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

选题理由 随着计算机网络及多媒体技术的广泛应用&#xff0c;互联网已成为高校办学的基础设施和必备条件&#xff0c;基于互联网的高校信息管理越来越综合化&#xff0c;越来越多的教学管理、行政管理工作将架构在互联网上&#xff0c;互联网正在变为学校实施教学、科研和管理…

设计模式(23)解释器模式

一、介绍&#xff1a; 1、定义&#xff1a;解释器(Interpreter)模式是一种对象的行为模式。给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;这个解释器使用该表示来解释语言中的句子。 2、组成结构&#xff1a; &#xff08;1&…