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