需求是实现类似2d窗体的磁力吸附的效果,有一个背景板,其中的各个窗口会自动吸附加粗样式,先预览一下效果。

unity 滑动吸附 unity自动吸附_Unity3d

本来想的有有两种实现思路:
第一种是利用画网格的方法,将背板看成一个矩形区域,计算出每个网格的中心点,然后存储起来,当拖动窗口松开鼠标的时候,计算所有网格中心点与窗体中心点的距离,将窗体放到与其距离最近的网格上。但这种方法适用性较低,当需要旋转窗体,然后再摆放的时候,网格就不好划分了。

第二种方法就是利用U3d的触发器来实现窗体四条边都有自动吸附的磁力效果,简单思路是给窗体每条边的外面都添加一个Box Coillder的碰撞器组件以及刚体组件,然后在触发器的OnTriggerStay2D方法中,编写磁力吸附相关的代码。
综合考虑和实现后,选用了第二种方法。下面写一下实现的简单步骤和细节。

  1. 创建相关的物体,并设置相应参数

unity 滑动吸附 unity自动吸附_unity 滑动吸附_02


Panel作为父物体是底板,四个PanelCol分别是底板边缘的四个碰撞体,用于实现拖动窗口到底板边缘的吸附效果。

Window是一个个的小窗体,它的四个子物体分别对应四条边上的碰撞体。这里要注意碰撞体中要加刚体组件,isTrigger要勾选上,同时刚体组件中的sleepmode属性要选为never sleep。

2.实现拖动窗体的效果
这里主要利用IPointerDownHandler, IPointerUpHandler两个系统的api接口,在OnPointerDown、OnPointerUp编写相应代码实现窗体随鼠标的拖动而移动

3.为四条边的碰撞体添加脚本实现吸附效果

因为窗体的摆放不固定,可能横放也可能竖放,不同情况下,吸附的位置不同,所以要考虑多种情况下的吸附。

A:横放窗体拖向横向窗体

unity 滑动吸附 unity自动吸附_Unity3d_03


B:竖向窗体拖向横向窗体

unity 滑动吸附 unity自动吸附_ide_04


C:横放窗体拖向竖向窗体

unity 滑动吸附 unity自动吸附_窗体吸附_05


D:竖向窗体拖向竖向窗体

unity 滑动吸附 unity自动吸附_磁力吸附_06


同时每一种情况下,拖动窗体与其他窗体不同边发生吸附时的情况也略有不同,这些都需要进行一些小计算。下面贴一下横放窗体上方碰撞体的代码。

void OnTriggerStay2D(Collider2D other)
    {
        //windowCollider为被碰撞的物体,windowParent为正在拖动的这个窗体本身,应该是拖动的窗体被吸附到windowCollider
        //上,dragTrue和dragFalse分别为windowParent和windowCollider的拖动脚本,我在其中设置了吸附标志变量用于控制窗体只有
        //在松开鼠标的那一刻才会发生吸附效果。
        
        windowCollider = other.transform.parent.GetComponent<RectTransform>();
        dragFalse = windowCollider.GetComponent<DragDemo>();
        if (other.gameObject.name == "Col_2")//通过判断与不同的碰撞体碰撞,以确定是哪一种碰撞情况
        {
            //通过判断pivot的值来判断该窗体是否经过旋转,此时是横放还是竖放。
            if (windowParent.GetComponent<RectTransform>().pivot == new Vector2(0, 1) && windowCollider.GetComponent<RectTransform>().pivot == new Vector2(0, 1))
            {
                //currentWindow代表当前拖动的窗体,即保证每次只有一个窗体的触发器启用,不然会出现抖动以及其他未知的问题
                //这点很重要,之前因为每个窗体都有四个碰撞体,且都挂有相同的脚步,互相很容易产生干扰,要保证每次吸附
                //只有一个碰撞体脚本生效,对其产生吸附,而这个窗体就是被拖动的那个窗体,
                //通过思考想到了先存储所有窗体的引用,然后在拖动一个窗体的同时,保存该拖动窗体的引用,在发生吸附作用
                //前进行判断,以保证一次只有一条边的吸附。
                /*情况A,横放窗体碰横放窗体*/
                if (dragTrue.absorbFlag == true && dragTrue.allowMove == false && windowParent.gameObject == PositionControl.currentWindow)
                {
                    
                    Debug.Log("OnTriggerStay2D:" + this.transform.name + "-" + other.gameObject.name);
                    //以下产生具体的吸附效果,需要根据吸附情况的不同进行相应计算和重写
                    tempVector = windowParent.localPosition;
                    y = windowCollider.localPosition.y;
                    tempVector.y = 0f;
                    windowParent.localPosition = tempVector + new Vector3(0, y - 100, 0);
                }
            } 
            /*情况D,竖放窗体碰撞竖放窗体*/
            if (windowParent.GetComponent<RectTransform>().pivot == new Vector2(0, 0) && windowCollider.GetComponent<RectTransform>().pivot == new Vector2(0, 0))
            {
                if (dragTrue.absorbFlag == true && dragTrue.allowMove == false && windowParent.gameObject == PositionControl.currentWindow)
                {
                    Debug.Log("OnTriggerStay2D:" + this.transform.name + "-" + other.gameObject.name);
                    //保y,改x
                    tempVector = windowParent.localPosition;
                    x = windowCollider.localPosition.x;
                    tempVector.x = 0f;
                    windowParent.localPosition = tempVector + new Vector3(x - dragFalse.itemLen, 0, 0);
                }
            }

        }
		/*情况B,竖放窗体碰撞横放窗体*/
        if (other.gameObject.name == "Col_3")
        {
            if (windowParent.GetComponent<RectTransform>().pivot == new Vector2(0, 0) && windowCollider.GetComponent<RectTransform>().pivot == new Vector2(0, 1))
            {
                if (dragTrue.absorbFlag == true && dragTrue.allowMove == false && windowParent.gameObject == PositionControl.currentWindow)
                {
                    Debug.Log("OnTriggerStay2D:" + this.transform.name + "-" + other.gameObject.name);
                    //保y,改x
                    tempVector = windowParent.localPosition;
                    x = windowCollider.localPosition.x;
                    tempVector.x = 0f;
                    windowParent.localPosition = tempVector + new Vector3(x - dragFalse.itemLen, 0, 0);
                }
            }
        }
		/*情况C,横放窗体碰撞竖放窗体*/
        if (other.gameObject.name == "Col_4")
        {
            if (windowParent.GetComponent<RectTransform>().pivot == new Vector2(0, 1) && windowCollider.GetComponent<RectTransform>().pivot == new Vector2(0, 0))
            {
                if (dragTrue.absorbFlag == true && dragTrue.allowMove == false && windowParent.gameObject == PositionControl.currentWindow)
                {
                    Debug.Log("OnTriggerStay2D:" + this.transform.name + "-" + other.gameObject.name);
                    //保x,改y
                    tempVector = windowParent.localPosition;
                    y = windowCollider.localPosition.y;
                    tempVector.y = 0f;
                    windowParent.localPosition = tempVector + new Vector3(0, y  - dragFalse.itemWidth, 0);
                }
            }
        }

     
    }

最后贴一下Demo的代码地址
https://github.com/AnyuFan/2WindowDemo