unity 揹包系統
前言
揹包系統這個地方坑點還是很多的,照著視訊做也費了很多勁.這個地方以後肯定是經常要碰到的,所以學到了什麼東西就記錄下來吧.
物品資訊管理
物品資訊管理的一大要求就是利用txt檔案儲存物品的屬性,這些屬性在揹包系統的管理中非常有用.物品屬性填寫的格式可以按照下面這個表格:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
id | 名稱 | icon名稱 | 型別(Drug) | 加血量 | 加魔量 | 出售價 | 購買價 |
程式碼如下:
public class ObjectInfoListManager : MonoBehaviour { public static ObjectInfoListManager _instance; public TextAsset objectInfoListText; public Dictionary<int, ObjectInfo> objectInfoDictById = new Dictionary<int, ObjectInfo>(); public Dictionary<string, ObjectInfo> objectInfoDictByName = new Dictionary<string, ObjectInfo>(); private void Awake() { _instance = this; ReadInfo(); //print(objectInfoDict.Keys.Count); } public ObjectInfo GetInfoById(int id) { ObjectInfo info = null; objectInfoDictById.TryGetValue(id, out info); return info; } public ObjectInfo GetInfoByIconName(string iconName) { ObjectInfo info = null; objectInfoDictByName.TryGetValue(iconName, out info); return info; } public void ReadInfo() { string text = objectInfoListText.text; string[] strArray = text.Split('\n'); foreach(string str in strArray) { ObjectInfo info = new ObjectInfo(); ObjectType type = ObjectType.Drug; string[] proArray = str.Split(','); int id = int.Parse(proArray[0]); string name = proArray[1]; string iconName = proArray[2]; string str_type = proArray[3]; switch (str_type) { case "Drug": type = ObjectType.Drug; break; case "Equip": type = ObjectType.Epuip; break; case "Mat": type = ObjectType.Mat; break; } if (type == ObjectType.Drug) { int hp = int.Parse(proArray[4]); int mp = int.Parse(proArray[5]); int price_sell = int.Parse(proArray[6]); int price_buy = int.Parse(proArray[7]); info.id = id; info.name = name; info.icon_name = iconName; info.type = type; info.hp = hp; info.mp = mp; info.price_sell = price_sell; info.price_buy = price_buy; } objectInfoDictById.Add(info.id, info); objectInfoDictByName.Add(info.icon_name, info); } } } public enum ObjectType { Drug, Epuip, Mat } public class ObjectInfo { public int id; public string name; public string icon_name; public ObjectType type; public int hp; public int mp; public int price_sell; public int price_buy; }
揹包介面
格子的設定
使用GridLayoutGroup來管理格子,可以控制格子的大小以及間隔.
揹包介面的彈出
揹包介面UI的動畫,現在一般用DOTween.一般比較常用的是 DoXX 之類的函式.目前我採用下面這段指令碼控制Ui動畫:
public void Show() { Tween tweener = this.transform.DOMove(targetPosition, duration); tweener.SetUpdate(true); tweener.SetEase(Ease.OutQuad); } public void Hide() { float beginTime = Time.time; Tween tweener = this.transform.DOMove(targetPosition + originPosition, duration); tweener.SetUpdate(true); tweener.SetEase(Ease.OutQuad); // 設定延時 if (Time.time - beginTime > duration) this.gameObject.SetActive(false); }
其中SetEase是設定動畫的播放形式,常用的是近對數曲線,也就是Ease.OutQuad
.
管理揹包系統裡的物品
UI拖拽功能
根據網上教程仿寫UI拖拽功能. 拖拽功能要繼承三個介面: IBeginDragHandler
, IDragHandler
, IEndDragHandler
,也有另外繼承 IPointerDownHandler
,IPointerUpHandler
的.
尤其要注意的是,UI的Position是RectTransform,所以直接用世界座標會出現問題.常見的解決方法是使用 Camera.main.ScreenToWorldPoint(Input.mousePosition)
或者 RectTransformUtility.ScreenPointToLocalPointInRectangle(imgRect, mouseDown, Camera.current, out mouseUguiPos)
.但是在我寫的程式碼中這些都出了點問題,後來發現最簡單的方法就行了
transform.position = Input.mousePosition;
程式碼如下:
public void OnBeginDrag(PointerEventData eventData)
{
if (canvasTra == null)
canvasTra = GameObject.Find("Canvas").transform;
lastParent = transform.parent; // 獲取當前的父物體
transform.SetParent(canvasTra); // 將canvas設為父物體
isRaycastLocationValid = false; // 當前物體隨著滑鼠移動,需要設為穿透才可以獲取被物體覆蓋的格子
}
public void OnDrag(PointerEventData eventData)
{
transform.position = Input.mousePosition;
}
public void OnEndDrag(PointerEventData eventData)
{
// 獲取終點位置滑鼠指向的可能物體
GameObject Go = eventData.pointerCurrentRaycast.gameObject;
// 如果指向物體存在
if (Go)
{
// 如果指向的是空格子
if (Go.tag.Equals(Tags.inventory_gird))
{
SetParentAndPosition(transform, Go.transform);
}
// 如果指向的是物品
else if(Go.tag.Equals(Tags.inventory_item))
{
// 滑鼠終點下也是一個物體時,我們預設交換位置
SetParentAndPosition(transform, Go.transform.parent);
SetParentAndPosition(Go.transform, lastParent);
if (transform.position == Go.transform.position)
{
Debug.LogError("物體重疊");
}
}
// 指向的位置無效
else
{
SetParentAndPosition(transform, lastParent);
}
}
// 如果沒有指向任何物體
else
{
SetParentAndPosition(transform, lastParent);
}
isRaycastLocationValid = true;
}
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
return isRaycastLocationValid;
}
private void SetParentAndPosition(Transform child, Transform parent)
{
child.SetParent(parent);
child.position = parent.position;
}
實現拾取物品的功能
這個標題我覺得取得還是不好.說白了這就是要實現物品數量的顯示,物品位置的不重複.
我將功能再次細分,分為下面三個:CreateNewItem\PlusItemNum\CheckItem.
CreateNewItem 主要負責根據預製體新增Item,以及設定建立物體的父節點,名字和位置等資訊.
PlusItemNum 主要負責新增物品的數量.
CheckItem 主要負責實時檢查格子上所存在物體的資訊,最好放在Update方法中.
程式碼如下:
public void CheckItem()
{
item = this.GetComponentInChildren<InventoryItem>();
if (item != null) // 如果不為空
{
SetGridOnShow(true, item.Id, item.Num);
}
else // 如果是空的,就清空資料
{
SetGridOnShow(false, 0, 0);
}
}
public void PlusItemNum(int addNum = 1)
{
item = this.GetComponentInChildren<InventoryItem>();
item.Num += addNum;
this.Num += addNum;
numLabel.text = this.Num.ToString();
}
public void CreateNewItem(int id, int num = 1)
{
// 根據id尋找預製體新增item
objectInfo = ObjectInfoListManager._instance.GetInfoById(id);
GameObject inventoryItem = Resources.Load(objectInfo.icon_name) as GameObject;
GameObject obj = GameObject.Instantiate(inventoryItem);
item = obj.GetComponent<InventoryItem>();
// 設定obj的父節點,位置,名字
obj.transform.SetParent(transform);
obj.transform.localPosition = Vector3.zero;
obj.name = objectInfo.icon_name;
// 設定item的id和num,用於檢查
item.Id = id;
item.Num = num;
}
public void SetGridOnShow(bool isShow, int id, int num = 1)
{
// 設定Grid和顯示
this.Num = num;
this.Id = id;
numLabel.text = num.ToString();
numLabel.gameObject.SetActive(isShow);
}
顯示物品的資訊
這裡需要建立一個資訊欄,然後在藥品建立的時候將資訊欄與該藥品關聯並隱藏.這裡用到了一個小技巧:因為預製體沒法在介面直接關聯,所以只好用指令碼尋找關聯;但我們又不能讓資訊欄一直顯示,所以需要將它"隱藏";因此我用到的小技巧是將資訊欄的 localScale 設為 zero,這樣可以達到目的又不至於藥品建立時關聯不到.
資訊欄的顯示和隱藏用普通的 OnMouseXX 不行,所以還是要繼承一些介面重寫函式.需要繼承的介面是IPointerEnterHandle
, IPointerExitHandle
.
程式碼如下
private void Update()
{
if (this.enabled)
{
inventoryDes = GameObject.Find("InventoryDes").GetComponent<InventoryDes>();
}
}
public void OnPointerEnter(PointerEventData eventData)
{
//Debug.Log("滑鼠進入");
inventoryDes.Show(this.Id);
}
public void OnPointerExit(PointerEventData eventData)
{
inventoryDes.Hide();
//Debug.Log("滑鼠離開");
}