NGUI学习笔记(五):背包拖拽效果

发表于2018-06-05
评论1 2.4k浏览
在NGUI中使用UIDragDropItem以及UIContainer两个内置脚本都可以实现拖拽或者交换物品,作为开发者只需要知道如何去根据自己的需求选择对应的函数。

先展示一下游戏项目中背包拖拽实现的效果:

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

场景截图:

脚本如下:
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信息已经给出了答案,对于那些细心的朋友可能早就发现了。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

1个评论

  • 永远陈运飞Felix 2018-06-07 1楼
    来的正是时候,雪中送炭啊