Unity3D — — UGUI之簡易揹包

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)

物品自己的代碼是在以前一篇博文的基礎上又添加了點東西:

Unity — — UGUI之揹包物品拖放

  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 }
InventoryManger
  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 }
InventoryItem
  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 }
ShowItemDetail

 

寫代碼時因爲筆者考慮不周,這裏簡單說下途中遇到的幾個問題以及解決方法:

(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的子物體

相關文章
相關標籤/搜索