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

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

添加背景

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

还可以改进的功能

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

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

最终效果

参考

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

UI & c# 扩展方法

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