经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » unity » 查看文章
Unity3D — — UGUI之简易背包
来源:cnblogs  作者:QQW的进化之旅  时间:2018/9/25 19:27:12  对本文有异议

Uinity版本:2017.3

最近在学Siki老师的《黑暗之光RPG》教程,由于教程内用的是NGUI实现,而笔者本人用的是UGUI,所以在这里稍微写一下自己的实现思路(大致上和NGUI一样

一、成品

先展现实现后的效果,如下:

 

功能简介:

物品的添加功能暂时通过摁下X来模拟(在Update()方法中实现)

实现的功能如图所示主要有以下几个

  根据相应的物品ID添加到背包中 / 如果已有物品则数量+1

  物品间的拖放交换

  摁住物品1秒后显示详细信息

 

二、代码

  代码分为两部分,背包整体Canvas(InventoryManger)和物品本身的Prefab(InventoryItem)

物品本身的代码是在之前一篇博文的基础上又添加了点东西:

Unity — — UGUI之背包物品拖放

  1. 1 using System.Collections;
  2. 2 using System.Collections.Generic;
  3. 3 using UnityEngine;
  4. 4 using UnityEngine.UI;
  5. 5
  6. 6 public class InventoryManger : MonoBehaviour {
  7. 7
  8. 8 public static InventoryManger _instance;
  9. 9 /// <summary>
  10. 10 ///无法获取还未实例化的物体(目前不知道具体方法)
  11. 11 /// 因此用public来获取物体的Prefab
  12. 12 /// </summary>
  13. 13 public InventoryItem item;
  14. 14 public List<GameObject> inventorySlotList = new List<GameObject>();
  15. 15 public Text coinText;
  16. 16
  17. 17 private Canvas inventory; //获取到背包,实现隐藏/显示功能
  18. 18 private int coinNum = 200;
  19. 19 private int count;
  20. 20 bool isFull = false; //判断背包是否满
  21. 21
  22. 22 public void Awake()
  23. 23 {
  24. 24 //获取到背包的Canvas组件来控制背包开关
  25. 25 //如果整个Gamobject设为UnActive则当背包关闭时无法设置物品信息(#Null# InventoryItem._instance.SetItemInfo(id))
  26. 26 inventory = this.GetComponent<Canvas>();
  27. 27 inventory.enabled = false;
  28. 28 _instance = this;
  29. 29
  30. 30 }
  31. 31
  32. 32 private void Start()
  33. 33 {
  34. 34 coinText.text = coinNum.ToString();
  35. 35 }
  36. 36
  37. 37 //模拟拾取
  38. 38 // Update is called once per frame
  39. 39 void Update()
  40. 40 {
  41. 41 if (Input.GetKeyDown(KeyCode.X))
  42. 42 {
  43. 43 int randomID = Random.Range(1001, 1004);
  44. 44 GetItemID(randomID);
  45. 45 }
  46. 46 }
  47. 47
  48. 48 /// <summary>
  49. 49 /// 通过ID号实例化Item
  50. 50 /// </summary>
  51. 51 /// <param name="id"> Item的ID号 </param>
  52. 52 public void GetItemID(int id)
  53. 53 {
  54. 54 //思路
  55. 55 //1.先查找所有格子找是否有物体
  56. 56 //1.1 如果有且ID相等 -> 数量+1,hasFoundItem设为true,跳出循环
  57. 57 //2.如果没找到相应物体,再次遍历所有格子
  58. 58 //2.1如果有空格子,实例化新物体
  59. 59 //2.2否则,背包满了
  60. 60
  61. 61 bool hasFoundItem = false;
  62. 62 foreach (GameObject temp in inventorySlotList)
  63. 63 {
  64. 64 InventoryItem slotChild = temp.GetComponentInChildren<InventoryItem>();
  65. 65 if (slotChild != null && slotChild.IdItem == id)
  66. 66 {
  67. 67 slotChild.ItemNumPlus();
  68. 68 isFull = false;
  69. 69 hasFoundItem = true;
  70. 70 Debug.Log(">>>> Add Num");
  71. 71 break;
  72. 72 }
  73. 73 }
  74. 74
  75. 75 if(hasFoundItem==false)
  76. 76 {
  77. 77 foreach (GameObject temp in inventorySlotList)
  78. 78 {
  79. 79 InventoryItem slotChild = temp.GetComponentInChildren<InventoryItem>();
  80. 80 if (slotChild == null && count < inventorySlotList.Count)
  81. 81 {
  82. 82 Instantiate(item, temp.transform);
  83. 83 InventoryItem._instance.SetItemInfo(id);
  84. 84 count++;
  85. 85 Debug.Log(">>>> Instantiate");
  86. 86 isFull = false;
  87. 87 break;
  88. 88 }
  89. 89 else //(slotChild!=null && slotChild.IdItem != id && && count < inventorySlotList.Count)
  90. 90 {
  91. 91 isFull = true;
  92. 92 Debug.Log("This slot has item : " + "slotChild.id = " + slotChild.IdItem + " slot = " + temp.name);
  93. 93 Debug.Log("New ID = " + id);
  94. 94 Debug.Log(">>> next loop");
  95. 95 }
  96. 96 }
  97. 97 }
  98. 98 //背包满了
  99. 99 if (isFull)
  100. 100 {
  101. 101 Debug.Log("bag full");
  102. 102 }
  103. 103
  104. 104 /// <summary>
  105. 105 /// 当没有交换物品功能时的实现方法
  106. 106 /// 按照顺序查找
  107. 107 /// </summary>
  108. 108 //foreach (GameObject temp in inventorySlotList)
  109. 109 //{
  110. 110 // InventoryItem slotChild = temp.GetComponentInChildren<InventoryItem>();
  111. 111
  112. 112 // //逻辑
  113. 113 // //if :1.当前格子无物体且背包未满 — — 实例化新的物品,full暂时设为false,跳出循环
  114. 114 // //else if :2.当前格子有物体且id相等 — — 物品数 + 1,full暂时设为false,跳出循环
  115. 115 // //else :3.当前格子有物体且id不等时 — — full暂时设为true,继续循环
  116. 116 // if (slotChild == null && count < inventorySlotList.Count)
  117. 117 // {
  118. 118 // Instantiate(item, temp.transform);
  119. 119 // InventoryItem._instance.SetItemInfo(id);
  120. 120 // count++;
  121. 121 // Debug.Log(">>>> Instantiate");
  122. 122 // isFull = false;
  123. 123 // break;
  124. 124 // }
  125. 125 // else if (slotChild != null && slotChild.IdItem == id)
  126. 126 // {
  127. 127 // slotChild.ItemNumPlus();
  128. 128 // isFull = false;
  129. 129 // Debug.Log(">>>> Add Num");
  130. 130 // break;
  131. 131 // }
  132. 132 // else //(slotChild!=null && slotChild.IdItem != id)
  133. 133 // {
  134. 134 // isFull = true;
  135. 135 // Debug.Log("This slot has item : " + "slotChild.id = " + slotChild.IdItem + " slot = " + temp.name);
  136. 136 // Debug.Log(">>> next loop");
  137. 137 // }
  138. 138 //}
  139. 139 }
  140. 140
  141. 141 //显示背包
  142. 142 public void ShowInventory()
  143. 143 {
  144. 144 inventory.enabled = true;
  145. 145 }
  146. 146
  147. 147 //隐藏背包
  148. 148 public void HideInventory()
  149. 149 {
  150. 150 inventory.enabled = false;
  151. 151 }
  152. 152 }
InventoryManger
  1. 1 using System.Collections;
  2. 2 using System.Collections.Generic;
  3. 3 using UnityEngine;
  4. 4 using UnityEditor;
  5. 5 using UnityEngine.EventSystems;
  6. 6 using UnityEngine.UI;
  7. 7
  8. 8 public class InventoryItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler,IPointerEnterHandler,IPointerDownHandler,IPointerUpHandler
  9. 9 {
  10. 10 public static InventoryItem _instance;
  11. 11
  12. 12 private float counter = 0f; //计算摁住时间
  13. 13 private bool isPress = false; //是否摁住鼠标
  14. 14 private int numItem = 1; //Item数量
  15. 15 private int NumItem
  16. 16 {
  17. 17 get
  18. 18 {
  19. 19 return numItem;
  20. 20 }
  21. 21 set
  22. 22 {
  23. 23 numItem = value;
  24. 24 }
  25. 25 }
  26. 26 //必须设为public,否则当未激活时获取不到ID
  27. 27 private int idItem = 0; //Item的ID
  28. 28 public int IdItem
  29. 29 {
  30. 30 get
  31. 31 {
  32. 32 return idItem;
  33. 33 }
  34. 34 set
  35. 35 {
  36. 36 idItem = value;
  37. 37 }
  38. 38 }
  39. 39
  40. 40 private Text numText; // Item 数量的text
  41. 41 private Image itemImage; //Item UI 图片
  42. 42 private Transform originalSlot;
  43. 43 private GameObject parent;
  44. 44 private GameObject item;
  45. 45 private float x_item;
  46. 46 private float y_item;
  47. 47 private Vector2 itemSize;
  48. 48 private bool isDragging = false; //默认设为false,否则OnPointerEnter每帧都会调用,会有bug
  49. 49
  50. 50 /// <summary>
  51. 51 /// 添加CanvasGroup组件,在物品拖动时blocksRaycasts设置为false;
  52. 52 /// 让鼠标的Pointer射线穿过Item物体检测到UI下层的物体信息
  53. 53 /// </summary>
  54. 54 private CanvasGroup itemCanvasGroup;
  55. 55
  56. 56 public void Awake()
  57. 57 {
  58. 58 //由于在实例化初始阶段就要获取,因此放在Awake里
  59. 59 //在Start()中会报空指针
  60. 60 itemImage = this.GetComponent<Image>();
  61. 61 numText = this.GetComponentInChildren<Text>();
  62. 62
  63. 63 _instance = this;
  64. 64 }
  65. 65 private void Start()
  66. 66 {
  67. 67 itemCanvasGroup = this.GetComponent<CanvasGroup>();
  68. 68 item = this.transform.gameObject;
  69. 69 x_item = item.GetComponent<Image>().GetPixelAdjustedRect().width; //Image的初始长宽
  70. 70 y_item = item.GetComponent<Image>().GetPixelAdjustedRect().height;
  71. 71
  72. 72 parent = GameObject.FindGameObjectWithTag("SlotGrid");
  73. 73 }
  74. 74
  75. 75
  76. 76 //增加物品数量并更新text显示
  77. 77 public void ItemNumPlus(int num = 1)
  78. 78 {
  79. 79 numItem+=num;
  80. 80 numText.text = numItem.ToString();
  81. 81 }
  82. 82
  83. 83 //设置物品具体信息
  84. 84 //1.根据ID得到物品信息(GetObjectInfoByID(id)方法)
  85. 85 //2.设置相应的ID号 、图片 、 数量text
  86. 86 public void SetItemInfo(int id)
  87. 87 {
  88. 88 ObjectInfo itemInfo = ObjectsInfo._instance.GetObjectInfoByID(id);
  89. 89 this.idItem = id; //设置id
  90. 90 itemImage.sprite = Resources.Load<Sprite>(itemInfo.icon_name);
  91. 91 numText.text = numItem.ToString();
  92. 92 }
  93. 93
  94. 94
  95. 95 public void OnPointerEnter(PointerEventData eventData)
  96. 96 {
  97. 97 //当鼠标在最外层时(移出背包,Canvas外)
  98. 98 //让物品回到原位
  99. 99 if (eventData.pointerCurrentRaycast.depth==0 && isDragging==true)
  100. 100 {
  101. 101 SetOriginalPos(this.gameObject);
  102. 102 return;
  103. 103 }
  104. 104
  105. 105 //Debug.Log(eventData.pointerCurrentRaycast.depth);
  106. 106 string objectTag = eventData.pointerCurrentRaycast.gameObject.tag;
  107. 107 Debug.Log("Raycast = "+objectTag);
  108. 108
  109. 109 //Item的拖放逻辑
  110. 110 if(objectTag!=null && isDragging==true)
  111. 111 {
  112. 112
  113. 113 if (objectTag == Tags.InventorySlot) //如果是空格子,则放置Item
  114. 114 {
  115. 115 SetCurrentSlot(eventData);
  116. 116 }
  117. 117 else if (objectTag == Tags.InventoryItem) //交换物品
  118. 118 {
  119. 119 SwapItem(eventData);
  120. 120 }
  121. 121 else //如果都不是则返回原位
  122. 122 {
  123. 123 SetOriginalPos(this.gameObject);
  124. 124 }
  125. 125 }
  126. 126 }
  127. 127
  128. 128 //把Item回归到原来位置
  129. 129 public void SetOriginalPos(GameObject gameobject)
  130. 130 {
  131. 131 gameobject.transform.SetParent(originalSlot);
  132. 132 gameobject.GetComponent<RectTransform>().anchoredPosition = originalSlot.GetComponent<RectTransform>().anchoredPosition;
  133. 133 itemCanvasGroup.blocksRaycasts = true;
  134. 134 }
  135. 135
  136. 136 //交换两个物体
  137. 137 //由于拖放中,正被拖放的物体没有Block RayCast
  138. 138 //具体思路:
  139. 139 //1.记录当前射线照射到的物体(Item2)
  140. 140 //2.获取Item2的parent的位置信息,并把item1放过去
  141. 141 //3.把Item2放到Item1所在的位置
  142. 142 public void SwapItem(PointerEventData eventData)
  143. 143 {
  144. 144 GameObject targetItem = eventData.pointerCurrentRaycast.gameObject;
  145. 145
  146. 146 //下面这两个方法不可颠倒,否则执行顺序不一样会出bug
  147. 147 //BUG:先把Item2放到了Item1的位置,此时Item1得到的位置信息是传递后的Item2的(原本Item1的位置)
  148. 148 //因此会把Item1也放到Item2下,变成都在原本Item1的Slot内
  149. 149 SetCurrentSlot(eventData);
  150. 150 SetOriginalPos(targetItem);
  151. 151 }
  152. 152
  153. 153 //设置Item到当前鼠标所在的Slot
  154. 154 public void SetCurrentSlot(PointerEventData eventData)
  155. 155 {
  156. 156 //如果Slot为空
  157. 157 if (eventData.pointerCurrentRaycast.gameObject.tag==Tags.InventorySlot)
  158. 158 {
  159. 159 Transform currentSlot= eventData.pointerCurrentRaycast.gameObject.transform;
  160. 160 this.transform.SetParent(currentSlot);
  161. 161 //如果只是transform position,图片会默认在左上角顶点处的Anchor
  162. 162 //因此这里用anchoredPosition让Item图片填充满Slot
  163. 163 this.GetComponent<RectTransform>().anchoredPosition = currentSlot.GetComponent<RectTransform>().anchoredPosition;
  164. 164 }
  165. 165 else if(eventData.pointerCurrentRaycast.gameObject.tag == Tags.InventoryItem)
  166. 166 {
  167. 167 Transform currentSlot = eventData.pointerCurrentRaycast.gameObject.transform.parent;
  168. 168 this.transform.SetParent(currentSlot);
  169. 169 this.GetComponent<RectTransform>().anchoredPosition = currentSlot.GetComponent<RectTransform>().anchoredPosition;
  170. 170 }
  171. 171 }
  172. 172
  173. 173 public void OnBeginDrag(PointerEventData eventData)
  174. 174 {
  175. 175 Debug.Log(eventData.pointerPress);
  176. 176 originalSlot = this.GetComponent<Transform>().parent; //每次拖拽开始前记录初始位置
  177. 177 isDragging = true;
  178. 178 itemCanvasGroup.blocksRaycasts = true;
  179. 179 item.transform.SetParent(parent.transform, false);
  180. 180
  181. 181 // 将item设置到当前UI层级的最下面(最表面,防止被同一层级的UI覆盖)
  182. 182 item.transform.SetAsLastSibling();
  183. 183
  184. 184 //Item的UI图片自适应改变大小
  185. 185 item.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, x_item);
  186. 186 item.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, y_item);
  187. 187 }
  188. 188
  189. 189 public void OnDrag(PointerEventData eventData)
  190. 190 {
  191. 191
  192. 192 itemCanvasGroup.blocksRaycasts = false;
  193. 193 DragPos(eventData);
  194. 194 Debug.Log(eventData.pointerPress);
  195. 195 }
  196. 196
  197. 197 public void OnEndDrag(PointerEventData eventData)
  198. 198 {
  199. 199 OnPointerEnter(eventData);
  200. 200 itemCanvasGroup.blocksRaycasts = true;
  201. 201 isDragging = false;
  202. 202 Debug.Log(eventData.pointerPress);
  203. 203 }
  204. 204
  205. 205 //获取鼠标当前位置,并赋给item
  206. 206 private void DragPos(PointerEventData eventData)
  207. 207 {
  208. 208 RectTransform RectItem = item.GetComponent<RectTransform>();
  209. 209 Vector3 globalMousePos;
  210. 210 if (RectTransformUtility.ScreenPointToWorldPointInRectangle(item.transform as RectTransform, eventData.position, eventData.pressEventCamera, out globalMousePos))
  211. 211 {
  212. 212 RectItem.position = globalMousePos;
  213. 213 }
  214. 214 }
  215. 215
  216. 216 //摁住1秒后显示文本信息
  217. 217 private void Update()
  218. 218 {
  219. 219 //Image的长宽需要在update里更新一下
  220. 220 //防止游戏中改变窗口大小后,拖拽时物品Image出现bug,比例失调
  221. 221 x_item = item.GetComponent<Image>().GetPixelAdjustedRect().width;
  222. 222 y_item = item.GetComponent<Image>().GetPixelAdjustedRect().height;
  223. 223
  224. 224 if (Input.GetMouseButton(0)&&isPress)
  225. 225 {
  226. 226 counter += Time.deltaTime;
  227. 227 Debug.Log(counter);
  228. 228 if(counter>=1)
  229. 229 {
  230. 230 ShowItemDetail._instance.ShowDetail(idItem);
  231. 231 }
  232. 232 }
  233. 233 }
  234. 234
  235. 235
  236. 236 public void OnPointerDown(PointerEventData eventData)
  237. 237 {
  238. 238 isPress = true;
  239. 239 Debug.Log("Down");
  240. 240 }
  241. 241 //释放鼠标后隐藏文本
  242. 242 public void OnPointerUp(PointerEventData eventData)
  243. 243 {
  244. 244 Debug.Log("Up");
  245. 245 counter = 0;
  246. 246 isPress = false;
  247. 247 ShowItemDetail._instance.HideDetail();
  248. 248 }
  249. 249 }
InventoryItem

 

写代码时由于笔者考虑不周,这里简单说下途中遇到的几个问题以及解决方法:

(1)背包的开启/关闭问题:把整个背包的勾选关掉后,是无法获取到未激活的物体,因此只能通过背包下的的Canvas组件来控制开关显示

(2)物品的添加:由于物品间有拖放功能,因此在添加物品时要先遍历整个背包格子来判断是否已有物体

(3)物品UI大小的自适应:物品在拖放过程中改变窗口大小图片会出错,因此在InventoryItem的Update()里实时获取UI大小

(4)物品信息的显示:和图中看到的一样,显示的位置还是有些尴尬,目前设置是显示在鼠标点击位置的右上角

 

三、实现思路

在这里笔者浅谈下自己对这个背包功能的理解

首先背包大致分为三个层级,依次为:背包整体 -> 格子 -> 物品 

背包:实现物品的添加功能,通过他获取其下各个子物体的信息(如Prefab等)

格子:存储物品的位置信息,不做具体的实现功能

物品:记录物品的信息和数量,不过这里笔者把拖放功能也在Item放里实现,通过当前item所在位置获取到格子的位置信息并设置相应的parent

大致设置如下:

格子和物品都是Prefab

其中有具体实现的只有InventoryCanvas和InventoryItem两个物体

 

然后通过InventoryCanvas获取到子物体的信息

接下来是在Item上添加脚本和CanvasGroup组件(在拖拽时blocks RayCasts设为false来获取到ItemUI层后的Slot层信息)

最后,这是在生成以及拖拽时Hierarchy层的变化

1.实例化ItemPrefab在相应格子内

2.在拖拽中把物品设置到到当前UI层最表面(最下方)

3.结束后设置成相应Slot的子物体

 

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号