1. 程式人生 > >unity 揹包系統

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("滑鼠離開");
    }