UGUI 实现Inventory(背包系统)

导语

NGUI的功能比unity自带的OnGUI强大很多,可以实现各种复杂的功能。其中背包功能是许多游戏所需要实现的,做出一个美观实用的背包系统也是一款商业游戏的基础。NGUI 的出现,2D/3D 无缝对接,菜单制作容易,效果好,几乎占领了 UI 100% 市场。 于是,Unity 重金聘请 NGUI 开发者开了半成品的 new UI 系统,尽管如此,Unity 5 已经可以实现 NGUI 的几乎所有效果。

今天,我就尝试着使用unity GUI来实现NGUI的官方例子的部分功能:http://www.tasharen.com/ngui/exampleX.html

目标效果

  • 背包的背景图片

  • 背包系统的格子

  • 背包格随鼠标转动

  • 物品进出背包(同理进出装备栏)

    暂未实现添加装备的效果,手头没有现成的Assets可供实现。

实现效果

现上一张最终实现的效果,然后开始按步骤分析。

过程

Canvas实现背包基础结构

首先,要利用Canvas来实现背包系统的背景和总体的框架。这一块包含了Canvas、Camera的配置。我是通过参考课程上所给的SampleUI来实现的。

新建Canvas

首先进入2D场景,新建一张Canvas命名为MyCanvas

设置Reander Mode、 Render Camera、 UI Scale mode、 Match如下

其中新建一个Camera命名为GUI Camera并设置Render Camera为它。

在MyCanvas里面新建一个Canvas子对象,命名为Inventory。并去除他的Canvas Scaler部件。这个Canvas将别用来作为存放背包栏和装备栏的画布。

建立背包格子

在Invertory里创建子两个对象Panel(面板)改名为Bag和Wear表示物品栏和装备栏,给它添加Gird LayOut Group组件,通过该组件可以实现自动布局。指定子对象元素大小,间隔。调整Pannel大小。锚点的话自己调整到合适的位置即可。

然后补充所需要的背包格子。直接使用Image类型。同理补充Wear装备栏。并添加适当的Text文本。

看到了如下效果:

效果还是比较简陋的。运行一下,Window还不能随鼠标移动。下面给Window添加代码。

window随鼠标运动

新建一个脚本TitlWindow.cs,将脚本挂在在两个Window对象上。可以实现以下效果。

1
using UnityEngine;
2
3
public class TiltWindow : MonoBehaviour
4
{
5
	public Vector2 range = new Vector2(5f, 3f);
6
7
	Transform mTrans;
8
	Quaternion mStart;
9
	Vector2 mRot = Vector2.zero;
10
11
	void Start ()
12
	{
13
		mTrans = transform;
14
		mStart = mTrans.localRotation;
15
	}
16
17
	void Update ()
18
	{
19
		Vector3 pos = Input.mousePosition;
20
21
		float halfWidth = Screen.width * 0.5f;
22
		float halfHeight = Screen.height * 0.5f;
23
		float x = Mathf.Clamp((pos.x - halfWidth) / halfWidth, -1f, 1f);
24
		float y = Mathf.Clamp((pos.y - halfHeight) / halfHeight, -1f, 1f);
25
		mRot = Vector2.Lerp(mRot, new Vector2(x, y), Time.deltaTime * 5f);
26
27
		mTrans.localRotation = mStart * Quaternion.Euler(-mRot.y * range.y, mRot.x * range.x, 0f);
28
	}
29
}

已经有了一个初步的效果。下面就是要添加背景和任务模型了。

添加背景

在场景下创建一个空对象命名为SF Scene Elements,用来放主摄像机和背景图片。在SF Scene Elements里面添加一个空对象,空对象命名为Backgound,并给它添加Sprite Rendera组件。

记得要将图片的格式设置为Sprite格式,然后在Backgound中的Sprite选择你所需要的背景图片。

摄像机调整

还有需要重新调整一下两个摄像机的参数。一个负责显示UI,一个负责显示背景图片及其他信息。

需要关注的就是Culling Mask这一个参数。一个显示UI,一个显示其余部分。至于位置大小,根据具体的情况进行调整,调整到可以效果良好即可。

可以看到这样子的初步效果。下面还需要添加角色。

添加角色

角色的话,这里就使用之前课程有使用到的盖伦且他带有Idel动作,也可以自己使用Assets store上的其他资源。直接将Garen资源包拖入项目,将Garen对象拖入场景,找到transformGaren,将贴图拖入Render。调整Garen的大小和位置。

状态机

接下来就是给盖伦添加动画。这里使用Animotor新动画,对应的动作要改成Generic类型的才可以。然后在盖伦资源文件目录下新建一个Animotor Controller设置如下,并赋值给Garen对象的动画控制器。通过状态机实现盖伦重复的idel动作。

点击运行,现在可以看到这样子的效果了:

完善背包功能

进行到这里,我们基本的效果都是有的了。而且在第一步已经实现了背包格子的功能,接下来就要实现背包最重要的功能。对物品的存储和拖拽功能了。

实现物品拖拽

我们新建一个对象树在Inventory里面来存放物品。每一个对象都为一个Image表示一个物品。存放我们所需要的物品。在Image组件里选择相应的Source Image作为装备贴图。

物品的拖拽,这是一个比较复杂的功能,我们需要实现这几个接口IBeginDragHandler, IDragHandler, IEndDragHandler。因为Unity的Event Trigger能够自动检测到你的拖拽操作,所以你需要做的就是当在物品上执行拖拽操作时,你要做什么,比如让物品跟随鼠标一起移动,物品移动到包包中的格子区域释放时,要把物品对齐好格子,又或者在空白区域释放时,物品要复位,又或者在另一物品上释放时两物品交换位置等。这就是你对物品拖拽操作所要做的内容,说着简单,实际的代码逻辑有点复杂。不过思路清晰就好。

代码如下:

DragItem.cs

1
using UnityEngine;
2
using System.Collections;
3
using UnityEngine.EventSystems;
4
using UnityEngine.UI;
5
6
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
7
{
8
    private Transform myTransform;
9
10
    private RectTransform myRectTransform;
11
    // 用于event trigger对自身检测的开关
12
    private CanvasGroup canvasGroup;
13
    // 拖拽操作前的有效位置,拖拽到有效位置时更新
14
    public Vector3 originalPosition;
15
    // 记录上一帧所在物品格子
16
    private GameObject lastEnter = null;
17
    // 记录上一帧所在物品格子的正常颜色
18
    private Color lastEnterNormalColor;
19
    // 拖拽至新的物品格子时,该物品格子的高亮颜色
20
    private Color highLightColor = Color.cyan;
21
22
    void Start()
23
    {
24
        myTransform = this.transform;
25
        myRectTransform = this.transform as RectTransform;
26
27
        canvasGroup = GetComponent<CanvasGroup>();
28
29
        originalPosition = myTransform.position;
30
    }
31
32
33
    void Update()
34
    {
35
36
    }
37
38
    public void OnBeginDrag(PointerEventData eventData)
39
    {
40
        canvasGroup.blocksRaycasts = false;//让event trigger忽略自身,这样才可以让event trigger检测到它下面一层的对象,如包裹或物品格子等
41
42
        lastEnter = eventData.pointerEnter;
43
        lastEnterNormalColor = lastEnter.GetComponent<Image>().color;
44
        originalPosition = myTransform.position;//拖拽前记录起始位置
45
46
        gameObject.transform.SetAsLastSibling();//保证当前操作的对象能够优先渲染,即不会被其它对象遮挡住
47
48
    }
49
50
    public void OnDrag(PointerEventData eventData)
51
    {
52
        Vector3 globalMousePos;
53
        if (RectTransformUtility.ScreenPointToWorldPointInRectangle(myRectTransform, eventData.position, eventData.pressEventCamera, out globalMousePos))
54
        {
55
            myRectTransform.position = globalMousePos;
56
57
        }
58
59
        GameObject curEnter = eventData.pointerEnter;
60
61
        bool inItemGrid = EnterItemGrid(curEnter);
62
        if (inItemGrid)
63
        {
64
            Image img = curEnter.GetComponent<Image>();
65
			lastEnter.GetComponent<Image>().color = lastEnterNormalColor;
66
            if (lastEnter != curEnter)
67
            {
68
				lastEnter.GetComponent<Image>().color = lastEnterNormalColor;
69
                lastEnter = curEnter;//记录当前物品格子以供下一帧调用
70
            }
71
72
            //当前格子设置高亮
73
            img.color = highLightColor;
74
        }
75
76
    }
77
78
    public void OnEndDrag(PointerEventData eventData)
79
    {
80
        GameObject curEnter = eventData.pointerEnter;
81
82
        //拖拽到的空区域中(如包裹外),恢复原位
83
        if (curEnter == null)
84
        {
85
            myTransform.position = originalPosition;
86
        }
87
        else
88
        {
89
            //移动至物品格子上
90
            if (curEnter.name == "UI_ItemGrid")
91
            {
92
                myTransform.position = curEnter.transform.position;
93
                originalPosition = myTransform.position;
94
95
				curEnter.GetComponent<Image>().color = lastEnterNormalColor;//当前格子恢复正常颜色
96
97
            }
98
            else
99
            {
100
                //移动至包裹中的其它物品上
101
                if (curEnter.name == eventData.pointerDrag.name && curEnter != eventData.pointerDrag)
102
                {
103
                    Vector3 targetPostion = curEnter.transform.position;
104
                    curEnter.transform.position = originalPosition;
105
                    myTransform.position = targetPostion;
106
                    originalPosition = myTransform.position;
107
                }
108
                else//拖拽至其它对象上面(包裹上的其它区域)
109
                {
110
                    myTransform.position = originalPosition;
111
                }
112
            }
113
        }
114
		lastEnter.GetComponent<Image>().color = lastEnterNormalColor;//上一帧的格子恢复正常颜色
115
        canvasGroup.blocksRaycasts = true;//确保event trigger下次能检测到当前对象
116
    }
117
118
119
120
    // 判断鼠标指针是否指向包裹中的物品格子
121
    // <param name="go">鼠标指向的对象</param>
122
    bool EnterItemGrid(GameObject go)
123
    {
124
        if (go == null)
125
        {
126
            return false;
127
        }
128
        return go.name == "UI_ItemGrid";
129
130
    }
131
132
133
}

还可以改进的功能

时间关系,所以没有实现随机穿上装备的功能。但是实现起来应该也不难,主要思路就是从游戏装备对象树里面随机抽取一个放到装备栏。

还有可以实现鼠标悬浮到物品商家可以显示相应的信息。在Unity中,只需要实现IPointerEnterHandler, IPointerExitHandler这两个接口。就可以实现了。

最终效果

参考

利用UGUI制作的包裹系统(一)

UI & c# 扩展方法

您的支持将鼓励我继续创作!