导语
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这两个接口。就可以实现了。