【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.mzph.cn/news/130287.shtml

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

相关文章

数字孪生技术与VR:创造数字未来

在当今数字化浪潮中&#xff0c;数字孪生和虚拟现实&#xff08;VR&#xff09;技术是两大亮点&#xff0c;它们以独特的方式相互结合&#xff0c;为各个领域带来了创新和无限可能。本篇文章将探讨数字孪生与VR之间的关系&#xff0c;以及它们如何共同开辟未来的新前景。 数字…

FreeRTOS_事件标志组

目录 1. 事件标志组简介 2. 创建事件标志组 2.1 函数 xEventGroupCreate() 2.2 函数 xEventGroupCreateStatic() 3. 设置事件位 3.1 函数 xEventGroupClearBits() 3.2 函数 xEventGroupClearBitsFromISR() 3.3 函数 xEventGroupSetBits() 3.4 函数 xEventGroupSetB…

项目实战:分页功能实战

1、在index.html添加点击事件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><link rel"stylesheet" href"style/index.css"><script src"scr…

点大商城V2版 2.5.2.1 全开源独立版 多小程序端+unipp安装教程

点大商城V2是一款采用全新界面设计支持多端覆盖的小程序应用&#xff0c;支持H5、微信公众号、微信小程序、头条小程序、支付宝小程序、百度小程序&#xff0c;本程序是点大商城V2独立版&#xff0c;包含全部插件&#xff0c;代码全开源&#xff0c;并且有VUE全端代码。分销&am…

万岳讲堂:抖音小程序开发入门指南

抖音小程序可以将开发者的创意带入这个热门的应用中。本文将带您深入了解抖音小程序的开发入门指南&#xff0c;帮助您开始在这一平台上构建自己的应用。 一、什么是抖音小程序&#xff1f; 抖音小程序是一种轻量级的应用程序&#xff0c;它可以在抖音中直接运行&#xff0c;无…

Java8 Stream API全面解析——高效流式编程的秘诀

文章目录 什么是 Stream Api?快速入门流的操作创建流中间操作filter 过滤map 数据转换flatMap 合并流distinct 去重sorted 排序limit 限流skip 跳过peek 操作 终结操作forEach 遍历forEachOrdered 有序遍历count 统计数量min 最小值max 最大值reduce 聚合collect 收集anyMatch…

python 成绩统计,输出及格率和优

题目描述&#xff1a; 小蓝给学生们组织了一场考试&#xff0c;卷面总分为100分&#xff0c;每个学生的得分都是一个0到100的整数。 如果得分至少是60分&#xff0c;则称为及格。如果得分至少为85分&#xff0c;则称为优秀。 请计算及格率和优秀率&#xff0c;用百分数表示&am…

虚拟机保护工具:Zerto Virtual Replication 10.0 U1 Crack

Zerto虚拟复制是为需要保护虚拟机和应用程序的企业设计的产品。通过通过连接到广域网或云到远程站点的复制来保护虚拟机。Zerto VR 2.0还可以与vCloud Director一起将虚拟机或虚拟机组复制到云端&#xff08;或从云端&#xff09;。 事实上&#xff0c;Zerto与33家云提供商合作…

Unity在Project右键点击物体之后获取到点击物体的名称

Unity在Project右键点击物体之后获取到点击物体的名称 描述&#xff1a; 在Unity的Project右键点击物体之后选择对应的菜单选项点击之后打印出物体的名称 注意事项 如果获取到文件或者预制体需要传递objcet类型&#xff0c;然后使用 GameObject.Instantiate((GameObject)se…

WiFi模块在智能家居中的应用与优化

智能家居技术的迅速发展已经改变了我们对家庭的定义。WiFi模块作为智能设备连接的核心&#xff0c;扮演着连接和控制智能家居生态系统的关键角色。本文将深入研究WiFi模块在智能家居中的应用&#xff0c;同时探讨如何通过优化来提升其性能和用户体验。 1. 智能家居中WiFi模块的…

第二十六章 BEV感知系列三(车道线感知)

前言 近期参与到了手写AI的车道线检测的学习中去&#xff0c;以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新&#xff0c;力求完整精炼&#xff0c;引人启示。所需前期知识&#xff0c;可以结合手写AI进行系统的学习。 BEV感知系列是对论文Delving into the De…

Jenkins项目部署

使用jenkins部署项目 简易版使用jenkins部署项目 将war包部署到tomcat中 将已有的war包部署到tomcat中(jenkins与tomcat在同一台主机) 点击Jenkins主页的新建任务 输入任务名称 选择构建一个自由风格的软件项目后点击确定 在构建内添加构建步骤&#xff0c;选择执行shell 输入…

回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测(多指标、多图)

回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测&#xff08;多指标、多图&#xff09; 目录 回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测&#xff08;多指标、多图&#xff09;效果一览基本介绍程序设计参考资料 效果一览…

数字IC后端实现 |TSMC 12nm 与TSMC 28nm Metal Stack的区别

下图为咱们社区IC后端训练营项目用到的Metal Stack。 芯片Tapeout Review CheckList 数字IC后端零基础入门Innovus学习教程 1P代表一层poly&#xff0c;10M代表有10层metal&#xff0c;M5x表示M2-M6为一倍最小线宽宽度的金属层&#xff0c;2y表示M7-M8为二倍最小线宽宽度的金…

npm的使用

package.json 快速生成package.json npm init -y “version”: “~1.1.0” 格式为&#xff1a;「主版本号. 次版本号. 修订号」。 修改主版本号是做了大的功能性的改动 修改次版本号是新增了新功能 修改修订号就是修复了一些bug dependencies "dependencies": {&…

redis rdb aof

appendonly yes # appendfsync always appendfsync everysec # appendfsync no E:\Document_Redis_Windows\redis-2.4.5-win32-win64\64bit appendonly.aof

Jetson NX FFmpeg硬件编解码实现

最近在用Jetson Xavier NX板子做视频处理&#xff0c;但是CPU进行视频编解码&#xff0c;效率比较地下。 于是便考虑用硬解码来对视频进行处理。 通过jtop查看&#xff0c;发现板子是支持 NVENC硬件编解码的。 1、下载源码 因为需要对ffmpeg进行打补丁修改&#xff0c;因此需…

Springboot JSP项目如何以war、jar方式运行

文章目录 一&#xff0c;序二&#xff0c;样例代码1&#xff0c;代码结构2&#xff0c;完整代码备份 三&#xff0c;准备工作1. pom.xml 引入组件2. application.yml 指定jsp配置 四&#xff0c;war方式运行1. 修改pom.xml文件2. mvn执行打包 五&#xff0c;jar方式运行1. 修改…

PPT制作指南

诸神缄默不语-个人CSDN博文目录 文章目录 1. SOP2. PPT的目标3. PPT素材4. 内容框架5. 设计细节本文撰写过程中使用到的参考资料 1. SOP 分析目标→收集素材→明确框架→视觉呈现 2. PPT的目标 演讲型PPT&#xff1a;字少图多 阅读型PPT&#xff1a;需要文字解释 分析维度&…

【深度学习基础】Pytorch框架CV开发(2)实战篇

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…