Uinity版本:2017.3html
最近在學Siki老師的《黑暗之光RPG》教程,因爲教程內用的是NGUI實現,而筆者本人用的是UGUI,因此在這裏稍微寫一下本身的實現思路(大體上和NGUI同樣)dom
1、成品ide
先展示實現後的效果,以下:oop
功能簡介:post
物品的添加功能暫時經過摁下X來模擬(在Update()方法中實現)ui
實現的功能如圖所示主要有如下幾個this
根據相應的物品ID添加到揹包中 / 若是已有物品則數量+1url
物品間的拖放交換spa
摁住物品1秒後顯示詳細信息指針
2、代碼
代碼分爲兩部分,揹包總體Canvas(InventoryManger)和物品自己的Prefab(InventoryItem)
物品自己的代碼是在以前一篇博文的基礎上又添加了點東西:
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEngine.UI; 5 6 public class InventoryManger : MonoBehaviour { 7 8 public static InventoryManger _instance; 9 /// <summary> 10 ///沒法獲取還未實例化的物體(目前不知道具體方法) 11 /// 所以用public來獲取物體的Prefab 12 /// </summary> 13 public InventoryItem item; 14 public List<GameObject> inventorySlotList = new List<GameObject>(); 15 public Text coinText; 16 17 private Canvas inventory; //獲取到揹包,實現隱藏/顯示功能 18 private int coinNum = 200; 19 private int count; 20 bool isFull = false; //判斷揹包是否滿 21 22 public void Awake() 23 { 24 //獲取到揹包的Canvas組件來控制揹包開關 25 //若是整個Gamobject設爲UnActive則當揹包關閉時沒法設置物品信息(#Null# InventoryItem._instance.SetItemInfo(id)) 26 inventory = this.GetComponent<Canvas>(); 27 inventory.enabled = false; 28 _instance = this; 29 30 } 31 32 private void Start() 33 { 34 coinText.text = coinNum.ToString(); 35 } 36 37 //模擬拾取 38 // Update is called once per frame 39 void Update() 40 { 41 if (Input.GetKeyDown(KeyCode.X)) 42 { 43 int randomID = Random.Range(1001, 1004); 44 GetItemID(randomID); 45 } 46 } 47 48 /// <summary> 49 /// 經過ID號實例化Item 50 /// </summary> 51 /// <param name="id"> Item的ID號 </param> 52 public void GetItemID(int id) 53 { 54 //思路 55 //1.先查找全部格子找是否有物體 56 //1.1 若是有且ID相等 -> 數量+1,hasFoundItem設爲true,跳出循環 57 //2.若是沒找到相應物體,再次遍歷全部格子 58 //2.1若是有空格子,實例化新物體 59 //2.2不然,揹包滿了 60 61 bool hasFoundItem = false; 62 foreach (GameObject temp in inventorySlotList) 63 { 64 InventoryItem slotChild = temp.GetComponentInChildren<InventoryItem>(); 65 if (slotChild != null && slotChild.IdItem == id) 66 { 67 slotChild.ItemNumPlus(); 68 isFull = false; 69 hasFoundItem = true; 70 Debug.Log(">>>> Add Num"); 71 break; 72 } 73 } 74 75 if(hasFoundItem==false) 76 { 77 foreach (GameObject temp in inventorySlotList) 78 { 79 InventoryItem slotChild = temp.GetComponentInChildren<InventoryItem>(); 80 if (slotChild == null && count < inventorySlotList.Count) 81 { 82 Instantiate(item, temp.transform); 83 InventoryItem._instance.SetItemInfo(id); 84 count++; 85 Debug.Log(">>>> Instantiate"); 86 isFull = false; 87 break; 88 } 89 else //(slotChild!=null && slotChild.IdItem != id && && count < inventorySlotList.Count) 90 { 91 isFull = true; 92 Debug.Log("This slot has item : " + "slotChild.id = " + slotChild.IdItem + " slot = " + temp.name); 93 Debug.Log("New ID = " + id); 94 Debug.Log(">>> next loop"); 95 } 96 } 97 } 98 //揹包滿了 99 if (isFull) 100 { 101 Debug.Log("bag full"); 102 } 103 104 /// <summary> 105 /// 當沒有交換物品功能時的實現方法 106 /// 按照順序查找 107 /// </summary> 108 //foreach (GameObject temp in inventorySlotList) 109 //{ 110 // InventoryItem slotChild = temp.GetComponentInChildren<InventoryItem>(); 111 112 // //邏輯 113 // //if :1.當前格子無物體且揹包未滿 — — 實例化新的物品,full暫時設爲false,跳出循環 114 // //else if :2.當前格子有物體且id相等 — — 物品數 + 1,full暫時設爲false,跳出循環 115 // //else :3.當前格子有物體且id不等時 — — full暫時設爲true,繼續循環 116 // if (slotChild == null && count < inventorySlotList.Count) 117 // { 118 // Instantiate(item, temp.transform); 119 // InventoryItem._instance.SetItemInfo(id); 120 // count++; 121 // Debug.Log(">>>> Instantiate"); 122 // isFull = false; 123 // break; 124 // } 125 // else if (slotChild != null && slotChild.IdItem == id) 126 // { 127 // slotChild.ItemNumPlus(); 128 // isFull = false; 129 // Debug.Log(">>>> Add Num"); 130 // break; 131 // } 132 // else //(slotChild!=null && slotChild.IdItem != id) 133 // { 134 // isFull = true; 135 // Debug.Log("This slot has item : " + "slotChild.id = " + slotChild.IdItem + " slot = " + temp.name); 136 // Debug.Log(">>> next loop"); 137 // } 138 //} 139 } 140 141 //顯示揹包 142 public void ShowInventory() 143 { 144 inventory.enabled = true; 145 } 146 147 //隱藏揹包 148 public void HideInventory() 149 { 150 inventory.enabled = false; 151 } 152 }
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEditor; 5 using UnityEngine.EventSystems; 6 using UnityEngine.UI; 7 8 public class InventoryItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler,IPointerEnterHandler,IPointerDownHandler,IPointerUpHandler 9 { 10 public static InventoryItem _instance; 11 12 private float counter = 0f; //計算摁住時間 13 private bool isPress = false; //是否摁住鼠標 14 private int numItem = 1; //Item數量 15 private int NumItem 16 { 17 get 18 { 19 return numItem; 20 } 21 set 22 { 23 numItem = value; 24 } 25 } 26 //必須設爲public,不然當未激活時獲取不到ID 27 private int idItem = 0; //Item的ID 28 public int IdItem 29 { 30 get 31 { 32 return idItem; 33 } 34 set 35 { 36 idItem = value; 37 } 38 } 39 40 private Text numText; // Item 數量的text 41 private Image itemImage; //Item UI 圖片 42 private Transform originalSlot; 43 private GameObject parent; 44 private GameObject item; 45 private float x_item; 46 private float y_item; 47 private Vector2 itemSize; 48 private bool isDragging = false; //默認設爲false,不然OnPointerEnter每幀都會調用,會有bug 49 50 /// <summary> 51 /// 添加CanvasGroup組件,在物品拖動時blocksRaycasts設置爲false; 52 /// 讓鼠標的Pointer射線穿過Item物體檢測到UI下層的物體信息 53 /// </summary> 54 private CanvasGroup itemCanvasGroup; 55 56 public void Awake() 57 { 58 //因爲在實例化初始階段就要獲取,所以放在Awake裏 59 //在Start()中會報空指針 60 itemImage = this.GetComponent<Image>(); 61 numText = this.GetComponentInChildren<Text>(); 62 63 _instance = this; 64 } 65 private void Start() 66 { 67 itemCanvasGroup = this.GetComponent<CanvasGroup>(); 68 item = this.transform.gameObject; 69 x_item = item.GetComponent<Image>().GetPixelAdjustedRect().width; //Image的初始長寬 70 y_item = item.GetComponent<Image>().GetPixelAdjustedRect().height; 71 72 parent = GameObject.FindGameObjectWithTag("SlotGrid"); 73 } 74 75 76 //增長物品數量並更新text顯示 77 public void ItemNumPlus(int num = 1) 78 { 79 numItem+=num; 80 numText.text = numItem.ToString(); 81 } 82 83 //設置物品具體信息 84 //1.根據ID獲得物品信息(GetObjectInfoByID(id)方法) 85 //2.設置相應的ID號 、圖片 、 數量text 86 public void SetItemInfo(int id) 87 { 88 ObjectInfo itemInfo = ObjectsInfo._instance.GetObjectInfoByID(id); 89 this.idItem = id; //設置id 90 itemImage.sprite = Resources.Load<Sprite>(itemInfo.icon_name); 91 numText.text = numItem.ToString(); 92 } 93 94 95 public void OnPointerEnter(PointerEventData eventData) 96 { 97 //當鼠標在最外層時(移出揹包,Canvas外) 98 //讓物品回到原位 99 if (eventData.pointerCurrentRaycast.depth==0 && isDragging==true) 100 { 101 SetOriginalPos(this.gameObject); 102 return; 103 } 104 105 //Debug.Log(eventData.pointerCurrentRaycast.depth); 106 string objectTag = eventData.pointerCurrentRaycast.gameObject.tag; 107 Debug.Log("Raycast = "+objectTag); 108 109 //Item的拖放邏輯 110 if(objectTag!=null && isDragging==true) 111 { 112 113 if (objectTag == Tags.InventorySlot) //若是是空格子,則放置Item 114 { 115 SetCurrentSlot(eventData); 116 } 117 else if (objectTag == Tags.InventoryItem) //交換物品 118 { 119 SwapItem(eventData); 120 } 121 else //若是都不是則返回原位 122 { 123 SetOriginalPos(this.gameObject); 124 } 125 } 126 } 127 128 //把Item迴歸到原來位置 129 public void SetOriginalPos(GameObject gameobject) 130 { 131 gameobject.transform.SetParent(originalSlot); 132 gameobject.GetComponent<RectTransform>().anchoredPosition = originalSlot.GetComponent<RectTransform>().anchoredPosition; 133 itemCanvasGroup.blocksRaycasts = true; 134 } 135 136 //交換兩個物體 137 //因爲拖放中,正被拖放的物體沒有Block RayCast 138 //具體思路: 139 //1.記錄當前射線照射到的物體(Item2) 140 //2.獲取Item2的parent的位置信息,並把item1放過去 141 //3.把Item2放到Item1所在的位置 142 public void SwapItem(PointerEventData eventData) 143 { 144 GameObject targetItem = eventData.pointerCurrentRaycast.gameObject; 145 146 //下面這兩個方法不可顛倒,不然執行順序不同會出bug 147 //BUG:先把Item2放到了Item1的位置,此時Item1獲得的位置信息是傳遞後的Item2的(本來Item1的位置) 148 //所以會把Item1也放到Item2下,變成都在本來Item1的Slot內 149 SetCurrentSlot(eventData); 150 SetOriginalPos(targetItem); 151 } 152 153 //設置Item到當前鼠標所在的Slot 154 public void SetCurrentSlot(PointerEventData eventData) 155 { 156 //若是Slot爲空 157 if (eventData.pointerCurrentRaycast.gameObject.tag==Tags.InventorySlot) 158 { 159 Transform currentSlot= eventData.pointerCurrentRaycast.gameObject.transform; 160 this.transform.SetParent(currentSlot); 161 //若是隻是transform position,圖片會默認在左上角頂點處的Anchor 162 //所以這裏用anchoredPosition讓Item圖片填充滿Slot 163 this.GetComponent<RectTransform>().anchoredPosition = currentSlot.GetComponent<RectTransform>().anchoredPosition; 164 } 165 else if(eventData.pointerCurrentRaycast.gameObject.tag == Tags.InventoryItem) 166 { 167 Transform currentSlot = eventData.pointerCurrentRaycast.gameObject.transform.parent; 168 this.transform.SetParent(currentSlot); 169 this.GetComponent<RectTransform>().anchoredPosition = currentSlot.GetComponent<RectTransform>().anchoredPosition; 170 } 171 } 172 173 public void OnBeginDrag(PointerEventData eventData) 174 { 175 Debug.Log(eventData.pointerPress); 176 originalSlot = this.GetComponent<Transform>().parent; //每次拖拽開始前記錄初始位置 177 isDragging = true; 178 itemCanvasGroup.blocksRaycasts = true; 179 item.transform.SetParent(parent.transform, false); 180 181 // 將item設置到當前UI層級的最下面(最表面,防止被同一層級的UI覆蓋) 182 item.transform.SetAsLastSibling(); 183 184 //Item的UI圖片自適應改變大小 185 item.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, x_item); 186 item.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, y_item); 187 } 188 189 public void OnDrag(PointerEventData eventData) 190 { 191 192 itemCanvasGroup.blocksRaycasts = false; 193 DragPos(eventData); 194 Debug.Log(eventData.pointerPress); 195 } 196 197 public void OnEndDrag(PointerEventData eventData) 198 { 199 OnPointerEnter(eventData); 200 itemCanvasGroup.blocksRaycasts = true; 201 isDragging = false; 202 Debug.Log(eventData.pointerPress); 203 } 204 205 //獲取鼠標當前位置,並賦給item 206 private void DragPos(PointerEventData eventData) 207 { 208 RectTransform RectItem = item.GetComponent<RectTransform>(); 209 Vector3 globalMousePos; 210 if (RectTransformUtility.ScreenPointToWorldPointInRectangle(item.transform as RectTransform, eventData.position, eventData.pressEventCamera, out globalMousePos)) 211 { 212 RectItem.position = globalMousePos; 213 } 214 } 215 216 //摁住1秒後顯示文本信息 217 private void Update() 218 { 219 //Image的長寬須要在update裏更新一下 220 //防止遊戲中改變窗口大小後,拖拽時物品Image出現bug,比例失調 221 x_item = item.GetComponent<Image>().GetPixelAdjustedRect().width; 222 y_item = item.GetComponent<Image>().GetPixelAdjustedRect().height; 223 224 if (Input.GetMouseButton(0)&&isPress) 225 { 226 counter += Time.deltaTime; 227 Debug.Log(counter); 228 if(counter>=1) 229 { 230 ShowItemDetail._instance.ShowDetail(idItem); 231 } 232 } 233 } 234 235 236 public void OnPointerDown(PointerEventData eventData) 237 { 238 isPress = true; 239 Debug.Log("Down"); 240 } 241 //釋放鼠標後隱藏文本 242 public void OnPointerUp(PointerEventData eventData) 243 { 244 Debug.Log("Up"); 245 counter = 0; 246 isPress = false; 247 ShowItemDetail._instance.HideDetail(); 248 } 249 }
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using UnityEngine.EventSystems; 5 using UnityEngine.UI; 6 7 public class ShowItemDetail : MonoBehaviour { 8 9 public static ShowItemDetail _instance; 10 private Image ItemDetailImage; 11 private Text ItemDetailText; 12 private bool isShow = false; 13 private void Awake() 14 { 15 _instance = this; 16 ItemDetailImage = this.GetComponent<Image>(); 17 ItemDetailText = this.GetComponentInChildren<Text>(); 18 ItemDetailImage.enabled = false; 19 ItemDetailText.enabled = false; 20 } 21 22 23 //顯示物體文本信息 24 public void ShowDetail(int id) 25 { 26 //文本信息位置在物品右上角 27 transform.position = new Vector3(Input.mousePosition.x+InventoryItem._instance.GetComponent<RectTransform>().rect.width, Input.mousePosition.y+ InventoryItem._instance.GetComponent<RectTransform>().rect.height, 0); 28 29 ItemDetailImage.enabled = true; 30 ItemDetailText.enabled = true; 31 ObjectInfo info = ObjectsInfo._instance.GetObjectInfoByID(id); 32 string content=""; 33 switch(info.type) 34 { 35 case ObjectType.Drug: 36 content = GetDrugDetail(info); 37 break; 38 case ObjectType.Equip: 39 content = GetEquipDetail(info); 40 break; 41 } 42 ItemDetailText.text = content; 43 } 44 //隱藏文本信息 45 public void HideDetail() 46 { 47 ItemDetailImage.enabled = false; 48 ItemDetailText.enabled = false; 49 } 50 //設置Drug的信息 51 public string GetDrugDetail(ObjectInfo info) 52 { 53 string str=""; 54 str += "名稱 :" + info.name + "\n"; 55 str += "恢復HP :" + info.hp + "\n"; 56 str += "恢復MP :" + info.mp + "\n"; 57 str += "購買價 :" + info.price_buy + "\n"; 58 str += "出售價 :" + info.price_sell + "\n"; 59 60 return str; 61 } 62 //設置裝備信息 63 public string GetEquipDetail(ObjectInfo info) 64 { 65 string str = ""; 66 str += "名稱 :" + info.name + "\n"; 67 switch(info.equipType) 68 { 69 case EquipType.Headgear: 70 str += "穿戴類型 : 頭盔\n"; 71 break; 72 case EquipType.Armor: 73 str += "穿戴類型 : 盔甲\n"; 74 break; 75 case EquipType.L_Hand: 76 str += "穿戴類型 : 左手\n"; 77 break; 78 case EquipType.R_Hand: 79 str += "穿戴類型 : 右手\n"; 80 break; 81 case EquipType.Shoe: 82 str += "穿戴類型 : 鞋子\n"; 83 break; 84 case EquipType.Accessory: 85 str += "穿戴類型 : 飾品\n"; 86 break; 87 } 88 switch(info.careerType) 89 { 90 case CareerType.Swordman: 91 str += "適用職業 : 劍士\n"; 92 break; 93 case CareerType.Magician: 94 str += "適用職業 : 法師\n"; 95 break; 96 case CareerType.Common: 97 str += "適用職業 : 通用\n"; 98 break; 99 } 100 str += "攻擊 : " + info.atk + "\n"; 101 str += "防護 : " + info.def + "\n"; 102 str += "速度 : " + info.spd + "\n"; 103 str += "購買價 :" + info.price_buy + "\n"; 104 str += "出售價 :" + info.price_sell + "\n"; 105 return str; 106 } 107 }
寫代碼時因爲筆者考慮不周,這裏簡單說下途中遇到的幾個問題以及解決方法:
(1)揹包的開啓/關閉問題:把整個揹包的勾選關掉後,是沒法獲取到未激活的物體,所以只能經過揹包下的的Canvas組件來控制開關顯示
(2)物品的添加:因爲物品間有拖放功能,所以在添加物品時要先遍歷整個揹包格子來判斷是否已有物體
(3)物品UI大小的自適應:物品在拖放過程當中改變窗口大小圖片會出錯,所以在InventoryItem的Update()裏實時獲取UI大小
(4)物品信息的顯示:和圖中看到的同樣,顯示的位置仍是有些尷尬,目前設置是顯示在鼠標點擊位置的右上角
3、實現思路
在這裏筆者淺談下本身對這個揹包功能的理解
首先揹包大體分爲三個層級,依次爲:揹包總體 -> 格子 -> 物品
揹包:實現物品的添加功能,經過他獲取其下各個子物體的信息(如Prefab等)
格子:存儲物品的位置信息,不作具體的實現功能
物品:記錄物品的圖片,數量以及ID號等獨立信息,不過這裏筆者把拖放功能也在Item放裏實現,經過當前item所在位置獲取到格子的位置信息並設置相應的parent
大體設置以下:
格子和物品都是Prefab
其中有具體實現的只有InventoryCanvas和InventoryItem兩個物體
而後經過InventoryCanvas獲取到子物體的信息
接下來是在Item上添加腳本和CanvasGroup組件(在拖拽時blocks RayCasts設爲false來獲取到ItemUI層後的Slot層信息)
最後,這是在生成以及拖拽時Hierarchy層的變化
1.實例化ItemPrefab在相應格子內
2.在拖拽中把物品設置到到當前UI層最表面(最下方)
3.結束後設置成相應Slot的子物體