InventoryManager一、背包系统
1.DataCollection:用于放置类/结构体
using UnityEngine;
[System.Serializable]
public class ItemDetails
{
public int itemID;
public string name;
public ItemType itemType;
public Sprite itemIcon;
public Sprite itemOnWorldSprite;
public int itemUseRadius;
public bool canPickUp;
public bool canDropped;
public bool canCarried;
public int itemPrice;
[Range(0,1)]
public float sellPercentage;
}
2.Enums:存储枚举型变量
public enum ItemType
{
Seed,Commodity,Furniture,
HoeTool,ChopTool,BreakTool,ReapTool,WaterTool,CollectTool,
ReapableScenery
}
3.ItemDataList_SO:
(1)_SO:表示其为ScriptObject
(2)代码:
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "ItemDataList_SO", menuName = "Inventory/ItemDataList")]
public class ItemDataList_SO : ScriptableObject
{
public List<ItemDetails> itemDetailsList;
}
二、 (Editor)使用 UI Toolkit 和 UI Builder 制作物品编辑器
效果如图:
三、创建 InventoryManager 和 Item
1.泛型单例
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T:Singleton<T>
{
private static T instance;
public static T Instance
{
get => instance;
}
protected virtual void Awake()
{
if (instance != null)
Destroy(gameObject);
else
instance = (T)this;
}
protected virtual void OnDestroy()
{
if (instance == this)
instance = null;
}
}
2.Item
using UnityEngine;
namespace MFarm.Inventory
{
public class Item : MonoBehaviour
{
public int itemID;
private SpriteRenderer spriteRenderer;
private BoxCollider2D coll;
private ItemDetails itemDetails;
private void Awake()
{
spriteRenderer = GetComponentInChildren<SpriteRenderer>();
coll = GetComponent<BoxCollider2D>();
}
private void Start()
{
if (itemID != 0)
{
Init(itemID);
}
}
public void Init(int ID)
{
itemID = ID;
//Inventory获得当前数据
itemDetails = InventoryManager.Instance.GetItemDetails(itemID);
if (itemDetails != null)
{
spriteRenderer.sprite = itemDetails.itemOnWorldSprite != null ? itemDetails.itemOnWorldSprite : itemDetails.itemIcon;
//修改碰撞体尺寸
Vector2 newSize = new Vector2(spriteRenderer.sprite.bounds.size.x, spriteRenderer.sprite.bounds.size.y);
coll.size = newSize;
coll.offset = new Vector2(0, spriteRenderer.sprite.bounds.center.y);
}
}
}
}
四、背包及拾取相关内容
1.拾取逻辑
using UnityEngine;
namespace MFarm.Inventory
{
public class ItemPickUp : MonoBehaviour
{
private void OnTriggerEnter2D(Collider2D other)
{
Item item = other.GetComponent<Item>();
if(item!=null)
{
if(item.itemDetails.canPickedup)
{
//拾取物品添加到背包
InventoryManager.Instance.AddItem(item, true);
}
}
}
}
}
public void AddItem(Item item, bool toDestory)
{
Debug.Log(GetItemDetails(item.itemID).itemID + "Name: " + GetItemDetails(item.itemID).itemName);
if (toDestory)
{
Destroy(item.gameObject);
}
}
2.背包
(1)数据结构
[System.Serializable]
public struct InventoryItem
{
public int itemID;
public int itemAmount;
}
(2)实现背包检查和添加物品
/// <summary>
/// 检查背包是否有空位
/// </summary>
/// <returns></returns>
private bool CheckBagCapacity()
{
for (int i = 0; i < playerBag.itemList.Count; i++)
{
if (playerBag.itemList[i].itemID == 0)
return true;
}
return false;
}
/// <summary>
/// 通过物品ID找到背包已有物品位置
/// </summary>
/// <param name="ID">物品ID</param>
/// <returns>-1则没有这个物品否则返回序号</returns>
private int GetItemIndexInBag(int ID)
{
for (int i = 0; i < playerBag.itemList.Count; i++)
{
if (playerBag.itemList[i].itemID == ID)
return i;
}
return -1;
}
/// <summary>
/// 在指定背包序号位置添加物品
/// </summary>
/// <param name="ID">物品ID</param>
/// <param name="index">序号</param>
/// <param name="amount">数量</param>
private void AddItemAtIndex(int ID, int index, int amount)
{
if (index == -1 && CheckBagCapacity()) //背包没有这个物品 同时背包有空位
{
var item = new InventoryItem { itemID = ID, itemAmount = amount };
for (int i = 0; i < playerBag.itemList.Count; i++)
{
if (playerBag.itemList[i].itemID == 0)
{
playerBag.itemList[i] = item;
break;
}
}
}
else //背包有这个物品
{
int currentAmount = playerBag.itemList[index].itemAmount + amount;
var item = new InventoryItem { itemID = ID, itemAmount = currentAmount };
playerBag.itemList[index] = item;
}
}
(3)制作 Action Bar UI
(4)背包内UI
相关代码
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class SlotUI : MonoBehaviour
{
[Header("组件获取")]
[SerializeField] private Image slotImage;
[SerializeField] private TextMeshProUGUI amountText;
[SerializeField] private Image slotHightlight;
[SerializeField] private Button button;
[Header("格子类型")]
public SlotType slotType;
public bool isSelected;
//物品信息
public ItemDetails itemDetails;
public int itemAmount;
private void Start()
{
isSelected = false;
if (itemDetails.itemID == 0)
{
UpdateEmptySlot();
}
}
/// <summary>
/// 更新格子UI和信息
/// </summary>
/// <param name="item">ItemDetails</param>
/// <param name="amount">持有数量</param>
public void UpdateSlot(ItemDetails item, int amount)
{
itemDetails = item;
slotImage.sprite = item.itemIcon;
itemAmount = amount;
amountText.text = amount.ToString();
button.interactable = true;
}
/// <summary>
/// 讲Slot更新为空
/// </summary>
public void UpdateEmptySlot()
{
if (isSelected)
{
isSelected = false;
}
slotImage.enabled = false;
amountText.text = string.Empty;
button.interactable = false;
}
}
(5)控制背包开闭、
private void Update()
{
if (Input.GetKeyDown(KeyCode.B))
{
OpenBagUI();
}
}
public void OpenBagUI()
{
bagOpened = !bagOpened;
bagUI.SetActive(bagOpened);
}
(6)背包物品选择高亮显示和动画
/// <summary>
/// 更新Slot高亮显示
/// </summary>
/// <param name="index">序号</param>
public void UpdateSlotHightlight(int index)
{
foreach (var slot in playerSlots)
{
if (slot.isSelected && slot.slotIndex == index)
{
slot.slotHightlight.gameObject.SetActive(true);
}
else
{
slot.isSelected = false;
slot.slotHightlight.gameObject.SetActive(false);
}
}
}
(7)实现拖拽物品交换数据和在地图上生成物品
部分代码:
public void OnEndDrag(PointerEventData eventData)
{
inventoryUI.dragItem.enabled = false;
Debug.Log(eventData.pointerCurrentRaycast.gameObject);
// Debug.Log(eventData.pointerCurrentRaycast.gameObject);
if (eventData.pointerCurrentRaycast.gameObject != null)
{
if (eventData.pointerCurrentRaycast.gameObject.GetComponent<SlotUI>() == null)
return;
var targetSlot = eventData.pointerCurrentRaycast.gameObject.GetComponent<SlotUI>();
int targetIndex = targetSlot.slotIndex;
//在Player自身背包范围内交换
if (slotType == SlotType.Bag && targetSlot.slotType == SlotType.Bag)
{
InventoryManager.Instance.SwapItem(slotIndex, targetIndex);
}
//清空所有高亮显示
inventoryUI.UpdateSlotHightlight(-1);
}
else //测试扔在地上
{
if (itemDetails.canDropped)
{
//鼠标对应世界地图坐标
var pos = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, -Camera.main.transform.position.z));
EventHandler.CallInstantiateItemInScene(itemDetails.itemID, pos);
}
}
}
/// <summary>
/// Player背包范围内交换物品
/// </summary>
/// <param name="fromIndex">起始序号</param>
/// <param name="targetIndex">目标数据序号</param>
public void SwapItem(int fromIndex, int targetIndex)
{
InventoryItem currentItem = playerBag.itemList[fromIndex];
InventoryItem targetItem = playerBag.itemList[targetIndex];
if (targetItem.itemID != 0)
{
playerBag.itemList[fromIndex] = targetItem;
playerBag.itemList[targetIndex] = currentItem;
}
else
{
playerBag.itemList[targetIndex] = currentItem;
playerBag.itemList[fromIndex] = new InventoryItem();
}
EventHandler.CallUpdateInventoryUI(InventoryLocation.Player, playerBag.itemList);
}