快速使用UGUI开发背包系统

如果你喜欢我们的教程,欢迎加入泰然网Unity交流群201505161

『泰然网』原创,转载请注明出处。

本篇教程由魏巍撰写。

魏巍现为大三学生,熟悉C++,C#,JavaScript,在校期间负责过网站制作,游戏开发,VR漫游开发等相关工作。

与以前的ngui所比较的话,ugui感觉更为简单方便,同样也与ngui有很多相同的功能,这里我们先来初步制作ugui的背包系统。

新建一个项目,创建一Image对象,同时会生成Canvas和EventSystem,修改Image的名字为Bg,我们可以将Bg的image组件修改为自己喜欢的图片作为背景,并调整RectTransform让其布满整个Canvas。

之后,我们需要创建整个背包的各个部件。我们在Bg下再创建一个Image作为背包的背景,改名为Inventory_Bg,创建一个空物体在Inventory_Bg下,并在空物体下创建一个Text对象,修改空物体名字为Title,作为背包的标题。完成这一步,我们再创建一个Image对象作为背包格子的框架,并相应添加Mask组件和Scroll Rect 组件,修改Image名字为Scroll Rect。

我们取消掉mask组件的ShowMaskGraphic选项后,就不会显示白色区域了,但这个区域会作为我们的背包格子布局区域。

ScrollRect下创建一个空物体,改名为GridList,添加Grid Layer Group组件,并调整GridList的宽度与ScrollRect一致,高度暂时设置为800左右(超出ScrollRect),设置锚点在顶部,调整位置。然后,我们创建Image名为Grid在GridList下,复制N个,可以看到Grid已经被自动排序了。然后我们再来微调,GridLayerGroupz组件下的cell size是设置我们每个Grid的长宽,spacing是间距,padding是内边距,其他几个选项也可以调整整个背包格子的布局位置等。为了方便我们自由的增加格子而不用每次因为格子的多少来调整高度,我们给ScrollRect添加一个Content Size Fitter,将Vertical Fit 设置为Preferred Size,这样我们的高度便能根据格子来自适应了。然后,我们把ScrollRect组件的Content设置为GridList,取消Horizontal选项。设置好后,运行试试,已经可以滑动了。

最后一点,我们稍微改改,在ScrollRect下创建一个UI-ScrollBar,将ScrollRect的ScrollRect组件的Vertical ScrollBar设置为我们刚才创建的ScrollBar,并将ScrollBar的Direction设置为Bottom To Top,调整Scrollbar的位置和长度。再一次运行,我们已经可以通过拖动ScrollBar来滑动我们的背包了。

这样呢,我们的背包系统就有一个初步的样子了,关于一些布局的锚点以及各个节点的位置,请大家自行调整。有一个简单背包的框架,滑动,网格布局。

之前我们创建了一个简单的背包框架,但是却没有物品。这里呢,我们提供一个简单的添加物品的一个思路。

首先,我们创建一个Image对象,并修改图片为物品的图片,保存为prefab。

然后,我们给上一篇中创建的GridList对象添加一个C#脚本,名为GridManager,脚本内容如图————

几乎所有的步骤呢我都写上了注释,另外呢虽然几乎是代码,但是呢依然用图片形式贴出来,也是想大家都自己敲一敲,防止一些和我一样懒惰的喜欢粘贴复制同学@_@.

另外呢对于初次接触的朋友呢,提醒一下要使用List,需要在上方添加using System.Collections.Generic;代码保存后呢还需要在Inspector面板中把所有的格子添加到我们代码创建的List对象中,prefab同理。

接下来,试试吧

这个简单的背包系统呢,差不多就这样了,大家可以通过修改、扩展甚至重写代码来完成各类物品的添加,另外与物品的交互呢就需要大家在自己的游戏总完成相应的功能了。

之前完成了一个背包以及添加物品,但是物品并不具备“物品”所该有的功能,除开每个游戏所需的特定的效果之外,也缺乏基本的移动拖拽功能等,这篇我们完善一下背包系统。先回顾一下之前的代码,这里我直接贴出来。

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GridManager : MonoBehaviour {
    public List<GameObject> grid;//格子列表,用来存储所有个物品格子
    public GameObject item;//物品对象,把prefab添加到这里
    void Update () {
        if(Input.GetKeyDown(KeyCode.K)){
            AddItem(item);//调用添加物品的方法
            }
    }
    public void AddItem(GameObject _item){
     //查找每个格子,寻找空格子的物体。
        for(int i=0;i<grid.Count;i++){
            //这里我们通过查找名字来寻找物体,判断格子内是否有物品存在
            //但这样就只能查找命名为该名字的物体,实际中我们可以通过tag或者其他属性来判断
           bool isHaving= grid[i].transform.FindChild("Sword(Clone)");
           //如果当前格子有物品存在         
           if(isHaving)
                continue;             
           else if(!isHaving){
                //当前的空格子
                Transform _i=grid[i].gameObject.GetComponent<RectTransform>();
                //创建一个物品对象
                GameObject go=(GameObject)Instantiate(_item); 
                //把创建的物品对象添加到格子下                        
                go.transform.SetParent(_i);
                //调整物品的位置位于格子中间
                go.transform.localPosition=Vector3.zero;
               break;
           }      
        }
    }

这段代码实现了根据按顺序给每个格子添加物品,如果我们的格子里面有物品,则会跳过,查看下一个格子是否能存放物品。注意,我们暂时没有写背包格子满载的情况,也没有判断物品是否能够叠加的功能。

那么,当一个背包里的东西杂乱无章的时候,我们就需要对其进行整理。这里我们先添加一个函数。

//在开头添加一个存储列表
public List<GameObject> c_grid;//备用列表,用于存储需要整理位置的物品
//整理物品函数
public void ClearUpGrid(){
        for (int i = 0; i < grid.Count; i++)
        {
            bool isHaving= grid[i].transform.FindChild("Sword(Clone)");
            if(isHaving){
                GameObject go=grid[i].transform.FindChild("Sword(Clone)").gameObject;
               c_grid.Add(go);//将当前格子内的物品存储到后备列表里面
               if(i==grid.Count-1)//当所有格子内的物品都存放到备用列表中后
                grid.Clear();//清空当前的背包
            }
        }
        for (int j = 0; j < c_grid.Count; j++)
        {
            //对于每个存储到后备列表中的物品,我们依次添加到格子列表中
            GameObject go=c_grid[j];
            go.transform.SetParent(grid[j].transform);
                //调整物品的位置位于格子中间
            go.transform.localPosition=Vector3.zero;   
        }
         //如果所有的物品都添加到了物品格子列表中,则清空后备列表
         if(j==c_grid.Count)
                c_grid.Clear();
    }

我们再给Update函数添加一句来调用整理背包的函数

if(Input.GetKeyDown(KeyCode.Space)){
                ClearUpGrid();
            }

分析一下
1、查询每个背包格子,如果有物品存在,则将该物品复制一份到备用列表中。
2、当查询到最后一个背包格子的时候,则清空当前背包格子内的物品
3、查询每个后备列表中的物品,依次添加到背包格子列表中。
4、当后备列表中所有物品都添加完成了,则清空后备列表。

这里也需要注意一下!!我们发现代码中再一次对整个背包列表进行了查询,与之前添加物品函数一样,总共我们就查询了两次,其实我们也可以当背包格子中有物品存在的时候,就将其添加到后备列表中存储,整理的时候直接清空格子再添加。但是当我们背包过大,物品过多的时候,这样子不一定好,因为这样每个物品就占了2个资源,如果我们按照这里的函数来执行的话,就只会在整理的时候占用资源。我们在做背包的时候,需要考虑如何安排这样的代码。这里提供这样的一个方法来参考。

另外,我们这里所有的物品都是一个,所以不存在类别关系,如果要对物品进行分类优先排序,我们需要对每个物品添加类别属性,通过类别属性来自定义优先级,在排序的时候进行优先排序。

背包整理的功能有了,接下来是对每个物品的处理。

首先,我们确定几项要点:
1、物品能够拖动
2、物品拖动后的层级关系
3、物品拖放的位置
4、拖动失败的处理

其实这是一个很简单的问题,我们事先给gridList添加了一个空物体GridManager作为父物体,先给我们的物品添加一个脚本

using UnityEngine;  
using UnityEngine.UI;  
using UnityEngine.EventSystems;  
using System.Collections;  
public class ItemDrag : MonoBehaviour, IPointerDownHandler,IPointerUpHandler,IDragHandler {  
    // 鼠标起点  
    private Vector2 originalLocalPointerPosition;     
    // 面板起点  
    private Vector3 originalPanelLocalPosition;  
    // 当前面板  
    private RectTransform panelRectTransform;  
    // 父节点,这个最好是UI父节点,因为它的矩形大小刚好是屏幕大小  
    public RectTransform parentRectTransform;  
    //格子列表
    private GameObject gridManager;//在gridlist之上添加的一个新的空物体作为父节点
    private GameObject originalGrid;//记录物品拖动前的位置
    private static int siblingIndex = 0;  
    void Awake () {  
        panelRectTransform = transform as RectTransform;  
        parentRectTransform = GameObject.FindGameObjectWithTag("ScrollRect").GetComponent<RectTransform>() as RectTransform;  
        gridManager=GameObject.Find("GridManager");   
    }  
    // 鼠标按下  
    public void OnPointerDown (PointerEventData data) {  
        //记录物品当前所在的格子信息
        originalGrid=panelRectTransform.parent.gameObject;
       //将物品放置在gridManager下,并设置层级,保证物品显示在整个背包层面之上
        panelRectTransform.SetParent(gridManager.transform);
        siblingIndex++;  
        panelRectTransform.transform.SetSiblingIndex(siblingIndex);  
        // 记录当前面板起点  
        originalPanelLocalPosition = panelRectTransform.localPosition;  
        // 通过屏幕中的鼠标点,获取在父节点中的鼠标点  
        // parentRectTransform:父节点  
        // data.position:当前鼠标位置  
        // data.pressEventCamera:当前事件的摄像机  
        // originalLocalPointerPosition:获取当前鼠标起点  
        RectTransformUtility.ScreenPointToLocalPointInRectangle (parentRectTransform, data.position, data.pressEventCamera, out originalLocalPointerPosition);  
    }  
    public void OnPointerUp(PointerEventData data){
       // transform.SetParent(gridlist.transform);
        RaycastHit2D hit = Physics2D.Raycast(Input.mousePosition,-Vector2.up); 
        if (hit.collider != null) { //如果射线检测到的gameobject为grid,就把当前物品放在grid节点下 
            if(hit.collider.gameObject.tag=="Grid"&&hit.collider.gameObject.transform.FindChild("Sword(Clone)")==null) 
               transform.parent=hit.transform;
               else
               {
//如果不是格子或没有检测到物体,则将物品放回到原来的格子内
                   transform.parent=originalGrid.transform;
               }
        }
        else
        {
          transform.parent=originalGrid.transform;
        }
    //重置物品位置
      transform.localPosition=Vector3.zero;
    }
    // 拖动  
    public void OnDrag (PointerEventData data) {  
        if (panelRectTransform == null || parentRectTransform == null){
            return;  
        }
        Vector2 localPointerPosition;  
        // 获取本地鼠标位置  
        if (RectTransformUtility.ScreenPointToLocalPointInRectangle (parentRectTransform, data.position, data.pressEventCamera, out localPointerPosition)) {  
            // 移动位置 = 本地鼠标当前位置 - 本地鼠标起点位置  
            Vector3 offsetToOriginal = localPointerPosition - originalLocalPointerPosition;  
            // 当前物品位置 = 物品起点 + 移动位置  
            panelRectTransform.localPosition = originalPanelLocalPosition + offsetToOriginal;  
        }  
       // ClampToWindow ();  
    }

本段代码是借用他人的代码做修改来使用的。我们注意一下代码。
首先,在脚本开头我们添加了这样一句

using UnityEngine.EventSystems;

这是饮用ugui的响应事件,同时也在MonoBehaviour后添加了三个接口名字,分别用逗号隔开。

public class ItemDrag : MonoBehaviour, IPointerDownHandler,
IPointerUpHandler,IDragHandler

添加后会发现报错,提示我们没有实现接口。这三个接口名分别对应以下三个函数,分别当鼠标按下时,鼠标松开时,以及物品拖动时。

public void OnPointerDown (PointerEventData data) {  }
public void OnPointerUp (PointerEventData data) {  }
public void OnDrag(PointerEventData data) { }

详细的功能在代码里注释有了。
另外关于RectTransformUtility.ScreenPointToLocalPointInRectangle这段代码呢可以去官网查询一下api了解一下@_@

再次注意,光是这样,我们是检测不到碰撞的,通过射线碰撞需要对每个物品格子添加Collider来检测,选中物品列表中的每个grid,添加BoxCollider2D组件,在Scene下调节Collider的大小。现在可以检测了。

别忘了给格子添加Tag为Grid。
运行一下,已经完成对背包的整理和对物品的拖放了

标签: unity, ugui

?>