Uinity版本:2017.3
最近在学Siki老师的《黑暗之光RPG》教程,由于教程内用的是NGUI实现,而笔者本人用的是UGUI,所以在这里稍微写一下自己的实现思路(大致上和NGUI一样)
一、成品
先展现实现后的效果,如下:

功能简介:
物品的添加功能暂时通过摁下X来模拟(在Update()方法中实现)
实现的功能如图所示主要有以下几个
根据相应的物品ID添加到背包中 / 如果已有物品则数量+1
物品间的拖放交换
摁住物品1秒后显示详细信息
二、代码
代码分为两部分,背包整体Canvas(InventoryManger)和物品本身的Prefab(InventoryItem)
物品本身的代码是在之前一篇博文的基础上又添加了点东西:

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)背包的开启/关闭问题:把整个背包的勾选关掉后,是无法获取到未激活的物体,因此只能通过背包下的的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的子物体
