NGUI学习笔记(五):背包拖拽效果
发表于2018-06-05
在NGUI中使用UIDragDropItem以及UIContainer两个内置脚本都可以实现拖拽或者交换物品,作为开发者只需要知道如何去根据自己的需求选择对应的函数。
先展示一下游戏项目中背包拖拽实现的效果:

文字说明:
- 希望被交换的物品在改变自己的位置时,有一段朝目标格子移动的位移动画,而不是很死板的瞬间改变position.
- 如图中所示,当前物品拖拽到了哪个格子上,则对应的格子会有相应的图形状态变化来提示用户。
场景截图:

脚本如下:
using UnityEngine; using System.Collections; using System.Collections.Generic; public class ExchangableItem : UIDragDropItem { /// <summary> /// 当前拖拽悬停在的那个Container; /// </summary> Transform dragOverContainer; protected override void Start () { base.Start (); } protected override void OnDragStart () { base.OnDragStart (); //每次开始拖拽时都将正在悬停的容器格子置为自身父物体,保证有一个正确的初始值 dragOverContainer = transform.parent; } protected override void OnPress (bool isPressed) { base.OnPress (isPressed); //当对某个Item按下时则其UIWidget(包括所有子物体)的深度都+2,为什么不是+1呢?因为被交换的Item会采用+1,这里要比被交换Item的深度高1. //反之当按压结束时还原深度 List<UIWidget> widgets = new List<UIWidget>(transform.GetComponentsInChildren<UIWidget>()); if (isPressed) { transform.GetComponent<UIWidget> ().depth+=2; foreach (UIWidget widget in widgets) { widget.depth+=2; } } else { transform.GetComponent<UIWidget> ().depth-=2; foreach (UIWidget widget in widgets) { widget.depth-=2; } } } protected override void OnDragDropMove (Vector2 delta) { base.OnDragDropMove (delta); Ray ray = UICamera.mainCamera.ScreenPointToRay (Input.mousePosition); RaycastHit hit; //当拖拽时,用射线检测Layer为Container的Collider,以此来获得被拖拽物在哪个容器的上方. if (Physics.Raycast (ray, out hit, Mathf.Infinity, (1 << LayerMask.NameToLayer ("Container")))) { if (hit.collider.transform != dragOverContainer) { UIPlayTween playTw = dragOverContainer.gameObject.GetComponent<UIPlayTween> (); playTw.resetOnPlay = true; //这里要吐槽一下NGUI内置的Tween功能,我本来想让处于拖拽物下方的容器做循环的颜色变换动画, //但是发现这样根本没法用UIPlayTween将其停止, //这也算是UIPlayTween的一大缺陷, //相比于DoTween简直弱爆了! //这里的反向播放是为了还原到原有状态. playTw.Play (false); print (dragOverContainer.name); dragOverContainer = hit.collider.transform; print (dragOverContainer.name); playTw = dragOverContainer.gameObject.GetComponent<UIPlayTween> (); playTw.resetOnPlay = true; playTw.Play (true); } } } protected override void OnDragEnd () { base.OnDragEnd (); //拖拽结束时也要动画进行反向播放来达到还原状态的目的 UIPlayTween playTw = dragOverContainer.gameObject.GetComponent<UIPlayTween> (); playTw.resetOnPlay = true; playTw.Play (false); } protected override void OnDragDropRelease (GameObject surface) { if (!cloneOnDrag) { // Re-enable the collider if (mButton != null) mButton.isEnabled = true; else if (mCollider != null) mCollider.enabled = true; else if (mCollider2D != null) mCollider2D.enabled = true; // Is there a droppable container? UIDragDropContainer container = surface ? NGUITools.FindInParents<UIDragDropContainer>(surface) : null; if (container != null) { //当这个容器是自身所在容器时直接还原 if (container.transform == mParent) { transform.localPosition = Vector3.zero; mParent.localScale = Vector3.one; return; } // Container found -- parent this object to the container Transform containerTrans = (container.reparentTarget != null) ? container.reparentTarget : container.transform; if (containerTrans.childCount > 0) { Transform parent = mParent; Transform child = containerTrans.GetChild (0); List<UIWidget> widgets = new List<UIWidget>(child.GetComponentsInChildren<UIWidget>()); child.GetComponent<UIWidget> ().depth++; foreach (UIWidget widget in widgets) { widget.depth++; } TweenPosition twpos = child.gameObject.AddComponent<TweenPosition> (); //将mParent中心点坐标转换为世界坐标,其实可以直接用mParent的世界坐标 Vector3 worldPos = mParent.TransformPoint (Vector3.zero); //将mParent的世界坐标转换到child的本地坐标系中 Vector3 localPos = child.InverseTransformPoint (worldPos); //TweenPosition动画是对物体的本地坐标进行改变,这一点很重要,很重要,很重要!!! twpos.to.Set (localPos.x, localPos.y, localPos.z); twpos.duration = 0.2f; //这里要再次吐槽NGUI自带的Tween,连在代码内指定一个动画曲线都这么困难!还要自己指定关键帧 //DoTween里直接封装好了非常多且使用的动画曲线,你需要通过枚举类型指定就可以了 //twpos.animationCurve = new AnimationCurve(new Keyframe[]()); twpos.Play (); twpos.AddOnFinished ( //新建一个代理 new EventDelegate ( //Lambada表达式申明匿名函数 () => { child.GetComponent<UIWidget> ().depth--; foreach (UIWidget widget in widgets) { widget.depth--; } //当动画播放完毕时改变父物体为拖拽物之前的父物体 //注意一定提前记录被拽物之前的父物体,因为这里相当于协同程序结束时的回调 child.parent = parent; child.localPosition = Vector3.zero; child.localScale = Vector3.one; } ) ); mTrans.parent = containerTrans; mTrans.localPosition = Vector3.zero; mTrans.localScale = Vector3.one; } } else { // No valid container under the mouse -- revert the item's parent mTrans.parent = mParent; mTrans.localPosition = Vector3.zero; mTrans.localScale = Vector3.one; } // Update the grid and table references mParent = mTrans.parent; mGrid = NGUITools.FindInParents<UIGrid>(mParent); mTable = NGUITools.FindInParents<UITable>(mParent); // Re-enable the drag scroll view script if (mDragScrollView != null) StartCoroutine(EnableDragScrollView()); // Notify the widgets that the parent has changed NGUITools.MarkParentAsChanged(gameObject); if (mTable != null) mTable.repositionNow = true; if (mGrid != null) mGrid.repositionNow = true; // We're now done OnDragDropEnd(); } else NGUITools.Destroy(gameObject); } }
最后我要补充一下当我做射线检测时,发现只要运行我原本设置好的Layer就会自动发生改变,这是为什么?
经过几次尝试终于发现了原因:

英文的Log信息已经给出了答案,对于那些细心的朋友可能早就发现了。