Unity-背包系统
简介
背包是每个成功游戏中不可缺少的,玩家获取的装备与道具将会放入背包,需要时再拿出来使用。如果没有背包来储存玩家在游戏中获得的武器和道具,或许游戏将会变得十分单一枯燥,出招方式一成不变。
有了背包系统,玩家才可以使用不同的武器,搭配不同的道具,使出不同的攻击搭配,从而提高游戏的多样性。
简单构思
首先我们思考一下背包系统的简单逻辑,玩家拾取物品后,背包中出现该物品,点击该物品之后又可以使用。
上述步骤的实现,需要我们完成两个层面的工作,一个是 背包的数据库 ,一个是 背包的UI 。
背包的数据库
数据库逻辑
现在需要构思如何拾取物品后记录数据。这里可以使用 ScriptableObject 来记录各个物品以及背包的数据,当玩家拾取物品后,将物品的信息传入背包的数据库进行记录。
创建数据库的步骤及细节
根据上述简单逻辑,可以得到下列创建数据库的步骤:
- 需要给 每个物品 和 每个背包 都创建自己的 ScriptableObject 数据
- 物品数据中包含自身的各项参数;背包数据需要有将物品数据存进背包的函数
- 同时需要创建 MonoBehavior脚本,并将其挂载在 物品和背包 上用于 控制自身 或 读取数据
- 物品脚本需要获得物品数据,并拥有将该数据放入背包的函数;背包脚本只需要拥有背包数据即可
- Player在触碰到物体后,物体本身的脚本将会触发,并通过函数将自己的数据加入背包中
(还需要注意以下细节:
- 物品数据中只有自身的个数,无法记录相同物品在背包中的个数,需要 创建一个类来保存物品数据及个数
- 不同的物品会需要不同的物品数据,需根据需求获取不同物品独有的 ScriptableObject数据
- 背包数据中,存储数据的算法需要做到最简(?)
代码样例_物品数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public enum ItemType{ Useable, Weapon, Armor }
[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item Data")] public class ItemData_SO : ScriptableObject { public bool stackable; public ItemType itemType; public string itemName; public Sprite itemIcon; public int itemAmount;
[TextArea] public string description = "";
[Header("Useable")] public UseableItemData_SO useableItemData;
[Header("Weapon")] public GameObject weaponPrefabs; public AttackData_SO attackData; }
|
代码样例_背包数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
[System.Serializable] public class InventroyItem { public ItemData_SO itemData; public int amount; }
[CreateAssetMenu(fileName = "New Inventory", menuName = "Inventory/Inventory Data")] public class InventoryData_So : ScriptableObject { public List<InventroyItem> items = new List<InventroyItem>(); public void AddItem(ItemData_SO itemData, int amount){ bool found = false; if(itemData.stackable){ foreach (var item in items){ if(item.itemData == itemData){ item.amount += amount; found = true; break; } } } if(!found){ for(int i = 0; i < items.Count; i++){ if(items[i].itemData == null){ items[i].itemData = itemData; items[i].amount = amount; break; } } } } }
|
代码样例_背包脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class InventroyManager : Singleton<InventroyManager> { [Header("Inventory Data")] public InventoryData_So inventoryData;
void Start() { inventoryUI.RefreshUI(); } }
|
代码样例_物品脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class ItemPickUp : MonoBehaviour { public ItemData_SO itemData; void OnTriggerEnter(Collider other) { if(other.CompareTag("Player")) { InventroyManager.Instance.inventoryData.AddItem(itemData, itemData.itemAmount);
Destroy(gameObject); } } }
|
背包的UI
完成上述背包的数据库后,还需要根据数据库完成背包的UI部分,让玩家能够更直观的管理背包
背包UI逻辑
要完成背包UI,就需要 从背包的数据库中获取背包中各项物品的各项数据(图片、数量……),然后在背包UI上显示。
创建背包UI的步骤及细节
- 编写背包UI的 代码 需要三层,第一层是 物品UI层(ItemUI),第二层是 物品栏(SlotHolder),第三层是背包层(ContainerUI),最后需要将背包层挂再在 InventoryManager 上供其他类调用
- 根据背包UI代码所分的三层,背包UI的 GameObject 同样需要分成三层。第一层 挂载ItemUI 作为物品UI层,同时子物体需要有 图片(Image) 跟 文本(Text);第二层 挂载SlotHolder 作为物品栏,子物体需要 ItemUI;第三层 挂载Container 作为背包层,子物体需要 n个 SlotHolder
(下面将会通过代码实例更详细的讲述
代码样例_ItemUI
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class ItemUI : MonoBehaviour { public Image icon = null; public Text amount = null; public InventoryData_So Bag { get; set; } public int Index { get; set; } = -1;
public void SetupItemUI(ItemData_SO item, int itemAmount) { if(itemAmount == 0) { Bag.items[Index].itemData = null; icon.gameObject.SetActive(false); return; }
if(item != null) { icon.sprite = item.itemIcon; amount.text = itemAmount.ToString(); icon.gameObject.SetActive(true); } else { icon.gameObject.SetActive(false); } } }
|
代码样例_SlotHolder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public enum SlotType { BAG, WEAPON, ARMOR, ACTION } public class SlotHolder : MonoBehaviour, IPointerClickHandler { public SlotType slotType; public ItemUI itemUI;
public void UpdateItem() { switch(slotType) { case SlotType.BAG: itemUI.Bag = InventroyManager.Instance.inventoryData; break; case SlotType.WEAPON: itemUI.Bag = InventroyManager.Instance.equipmentData; break; case SlotType.ARMOR: itemUI.Bag = InventroyManager.Instance.equipmentData; break; case SlotType.ACTION: itemUI.Bag = InventroyManager.Instance.actionData; break; }
var item = itemUI.Bag.items[itemUI.Index]; itemUI.SetupItemUI(item.itemData, item.amount); } }
|
代码样例_ContainerUI
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class ContainerUI : MonoBehaviour { public SlotHolder[] slotHolders; public void RefreshUI() { for(int i = 0; i < slotHolders.Length; i++) { slotHolders[i].itemUI.Index = i; slotHolders[i].UpdateItem(); } } }
|
实现拖拽物品
实现物品拖拽,可以直接使用Unity自带的拖拽物品函数,只需要实现以下几个接口:
1 2 3 4 5 6 7 8
| public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { public void OnBeginDrag(PointerEventData eventData){} public void OnDrag(PointerEventData eventData){}
public void OnEndDrag(PointerEventData eventData){} }
|
(这几个接口中传入的参数 eventData 包含了关于鼠标的各项参数,有需要的可以查阅Unity官方手册
需要知道的是:
- 需要拖拽的物品是挂载有 ItemUI 的 GameObject ,所以该类需要挂载在上述 GameObject 上
- 为了防止玩家将物品拖拽进错的位置,需要 记录物品原本的位置 并在其 位置错误之后,将物品复原
- 在拖动结束后,需要进行一系列判断,保证物品交换无误(直接交换、堆叠 或 回到原位)
(下面用代码样例进行详细说明
代码样例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| [RequireComponent(typeof(ItemUI))] public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { ItemUI currentItemUI; SlotHolder currentHolder; SlotHolder targetHolder; void Awake() { currentItemUI = GetComponent<ItemUI>(); currentHolder = GetComponentInParent<SlotHolder>(); }
public void OnBeginDrag(PointerEventData eventData) { InventroyManager.Instance.currentDrag = new InventroyManager.DragData(); InventroyManager.Instance.currentDrag.originalHolder = GetComponentInParent<SlotHolder>(); InventroyManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent; transform.SetParent(InventroyManager.Instance.DragCanvas.transform, true); }
public void OnDrag(PointerEventData eventData) { transform.position = eventData.position; }
public void OnEndDrag(PointerEventData eventData) { if(EventSystem.current.IsPointerOverGameObject()) {
if(InventroyManager.Instance.CheckInActionUI(eventData.position) || InventroyManager.Instance.CheckInEquipmentUI(eventData.position) || InventroyManager.Instance.CheckInInventoryUI(eventData.position)) { if(eventData.pointerEnter.gameObject.GetComponent<SlotHolder>()) { targetHolder = eventData.pointerEnter.gameObject.GetComponent<SlotHolder>(); } else { targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent<SlotHolder>(); }
switch(targetHolder.slotType) { case SlotType.BAG: SwapItem(); break; case SlotType.WEAPON: if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Weapon) { SwapItem(); } break; case SlotType.ARMOR: if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Armor) { SwapItem(); } break; case SlotType.ACTION: if(currentItemUI.Bag.items[currentItemUI.Index].itemData.itemType == ItemType.Useable) { SwapItem(); } break; }
currentHolder.UpdateItem(); targetHolder.UpdateItem(); } } transform.SetParent(InventroyManager.Instance.currentDrag.originalParent);
RectTransform t = transform as RectTransform;
t.offsetMax = Vector2.one * 40; t.offsetMin = -Vector2.one * 40; }
public void SwapItem() { var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index]; var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];
bool isSameItem = tempItem.itemData == targetItem.itemData;
if(isSameItem && targetItem.itemData.stackable) { targetItem.amount += tempItem.amount; tempItem.itemData = null; tempItem.amount = 0; } else { currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem; targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem; } } }
|