zoukankan      html  css  js  c++  java
  • 使用UGUI原生开发连线/画线

    先上效果图:

     红色就是连线的效果,可以用在状态机之间的连线,也可以通过简单修改,改为在图片上涂鸦。

    注:如果使用的是LineRenderer实现的话虽然也能达到这个效果,但是不能与原生UI一致,导致不能使用遮罩,层级只能在其它UI的最上层或者最下层。

    实现方法:

       通过继承MaskableGraphic类,重写OnPopulateMesh(VertexHelper vh)方法,这样就可以在UGUI上自定义各种各样的自定义mesh了。

    实现绘制mesh的方法:

      ①可以使用 VertexHelper.AddVert(UIVertex v) 这个方法增加好点位后还需要设置Triangle,调用VertexHelper.SetTriangle(int idx0, int idx1, int idx2)方法设置。

      ②也可以直接调用VertexHelper.AddUIVertexQuad(UIVertex[] verts)  此方法可以直接生成一个四边形,参数传入四边形的四个顶点,传入顺序是顺时针。

     示例图:

                 v0-------v1
                  |            |
                 v3-------v2
                        ↑
                  lineWidth

      这里使用的是第二种方法。参数传入顺序为V0 V1 V2 V3,先设置一个线条的粗细值lineWidth=2,很容易的就知道四边形四个点的坐标信息,这样就能绘制出一个四边形了。

      然而这个线并不是直线,是弯曲的线,这样才自然。既然实现了一个四边形的绘制,那么如果想要组成线段,就需要对整条线段进行切分,

    这条线右若干个四边形组成,如果要弯曲的话,这里引用的是三阶贝塞尔曲线。

      贝塞尔曲线详情点击:https://www.cnblogs.com/yzxhz/p/13802952.html

      

    注意的问题:

      1.曲线有了,这里还有一点问题,在转弯折角处如果不进行处理的话,折角处外边会出现缺口,

      解决办法:

      将每个四边形的第一条边与上一个四边形的最后一条边(也就是两个四边形的接缝处的两条边)斜率保持相同即可。

      2.当两个点左右换位置的时候,线段会出现绘制错乱问题。

      解决办法:

      注意两个点位的左右顺序,通过判断x的大小来判断哪个点在左边,哪个点在右边。

    左右互换的话,两个贝塞尔的中间点位也要进行对称处理,还有初始第一条边的斜率向上改为向下。

    增加射线检测:

      线段绘制好后需要对线段部分和透明部分区分开可点击区域,通过继承ICanvasRaycastFilter接口,

    实现IsRaycastLocationValid(Vector2 sp, Camera eventCamera)方法,可以达到此目的。

      只要判断屏幕上的点在每一小段的四边形内部即可,如果存在一个四边形内部,就说明当前鼠标包含在线段内部了,就可以触发射线检测了。

    具体实现的方式看下面源码即可。

    完整代码如下:

    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class DrawLine : MaskableGraphic, ICanvasRaycastFilter
    {
    
        List<List<UIVertex>> vertexQuadList = new List<List<UIVertex>>();
    
        public float lineWidth = 2;
    
        public Vector3 startPos;
        public Vector3 endPos;
    
        protected override void OnPopulateMesh(VertexHelper vh)
        {
            vh.Clear();
            for (int i = 0; i < vertexQuadList.Count; i++)
            {
                vh.AddUIVertexQuad(vertexQuadList[i].ToArray());
            }
        }
    
    
        void Update()
        {
            //设定rect大小正好能圈住线
            rectTransform.position = endPos + (startPos - endPos) / 2f;
            rectTransform.sizeDelta = new Vector2(Mathf.Abs(endPos.x - startPos.x), Mathf.Abs(endPos.y - startPos.y));
            AddVert();
        }
    
        /// <summary>
        /// 增加点位
        /// </summary>
        public void AddVert()
        {
    
            vertexQuadList.Clear();
    
            //贝塞尔的两个中间点位,Start点位在左边和右边的情况区分
            Vector3 tempMiddle1Pos = startPos + Vector3.Distance(startPos, endPos) / 5f * ((startPos.x < endPos.x) ? Vector3.right : Vector3.left);
            Vector3 tempMiddle2Pos = endPos + Vector3.Distance(startPos, endPos) / 5f * ((startPos.x < endPos.x) ? Vector3.left : Vector3.right);
            //增加贝塞尔点位
            List<Vector3> bPoints = CreatThreeBezierCurve(startPos, endPos, tempMiddle1Pos, tempMiddle2Pos);
            //初始的方向设置为向上垂直方向
            Vector3 lastVerticalDir = (startPos.x < endPos.x) ? Vector3.up : Vector3.down;
            for (int i = 0; i < bPoints.Count - 1; i++)
            {
                //当前的线段方向
                Vector3 curDir = bPoints[i] - bPoints[i + 1];
                //通过当前线段方向计算垂直方向
                Vector3 curVerticalDir = Vector3.Cross(curDir.normalized, Vector3.forward).normalized;
                //已知两个相邻点位,并且已知线段的粗细,可以计算出四边形
                List<UIVertex> vertexQuad = GetTwoPointMesh(bPoints[i], bPoints[i + 1], lastVerticalDir, curVerticalDir);
    
                vertexQuadList.Add(vertexQuad);
                lastVerticalDir = curVerticalDir;
            }
            SetVerticesDirty();
        }
    
    
        /// <summary>
        /// 获得两个点之间的面片
        /// </summary>
        /// <param name="vertexQuad"></param>
        private List<UIVertex> GetTwoPointMesh(Vector3 _startPos, Vector3 _endPos, Vector3 beforeTwoNodeVerticalDir, Vector3 nextTwoNodeVerticalDir)
        {
            List<UIVertex> vertexQuad = new List<UIVertex>();
    
            //  v0-------v1
            //   |              |
            //  v3-------v2
            ////       width
    
            Vector3 v0 = _startPos - beforeTwoNodeVerticalDir * lineWidth - rectTransform.position;
            Vector3 v1 = _startPos + beforeTwoNodeVerticalDir * lineWidth - rectTransform.position;
    
            Vector3 v3 = _endPos - nextTwoNodeVerticalDir * lineWidth - rectTransform.position;
            Vector3 v2 = _endPos + nextTwoNodeVerticalDir * lineWidth - rectTransform.position;
    
            UIVertex uIVertex = new UIVertex();
            uIVertex.position = v0;
            uIVertex.color = color;
            vertexQuad.Add(uIVertex);
            UIVertex uIVertex1 = new UIVertex();
            uIVertex1.position = v1;
            uIVertex1.color = color;
            vertexQuad.Add(uIVertex1);
    
            UIVertex uIVertex2 = new UIVertex();
            uIVertex2.position = v2;
            uIVertex2.color = color;
            vertexQuad.Add(uIVertex2);
    
            UIVertex uIVertex3 = new UIVertex();
            uIVertex3.position = v3;
            uIVertex3.color = color;
            vertexQuad.Add(uIVertex3);
    
            return vertexQuad;
        }
    
    
        /// <summary>
        /// 设置线段的碰撞
        /// </summary>
        /// <param name="sp"></param>
        /// <param name="eventCamera"></param>
        /// <returns></returns>
        public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
        {
            bool isEnterMesh = false;
            for (int i = 0; i < vertexQuadList.Count; i++)
            {
                if (IsContainInQuad(sp, transform.position + vertexQuadList[i][0].position, transform.position + vertexQuadList[i][1].position, transform.position + vertexQuadList[i][2].position, transform.position + vertexQuadList[i][3].position))
                {
                    isEnterMesh = true;
                    break;
                }
            }
            return isEnterMesh;
        }
    
        bool IsContainInQuad(Vector3 point, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4)
        {
            //      p1-----p2
            //      |       |
            //      | point |
            //      |       |
            //      p4-----p3
    
            Vector2 p1p2 = p1 - p2;
            Vector2 p1p = p1 - point;
            Vector2 p3p4 = p3 - p4;
            Vector2 p3p = p3 - point;
    
            Vector2 p4p1 = p4 - p1;
            Vector2 p4p = p4 - point;
            Vector2 p2p3 = p2 - p3;
            Vector2 p2p = p2 - point;
    
            bool isBetweenP1P2_P3P4 = CrossAB(p1p2, p1p) * CrossAB(p3p4, p3p) > 0;
            bool isBetweenP4P1_P2P3 = CrossAB(p4p1, p4p) * CrossAB(p2p3, p2p) > 0;
    
            return isBetweenP1P2_P3P4 && isBetweenP4P1_P2P3;
    
        }
    
        float CrossAB(Vector2 a, Vector2 b)
        {
            return a.x * b.y - b.x * a.y;
        }
    
        public float nultiple = 8;
        /// <summary>
        /// 三阶贝塞尔
        /// </summary>
        /// <param name="startPoint"></param>
        /// <param name="endPoint"></param>
        /// <param name="middlePoint1"></param>
        public List<Vector3> CreatThreeBezierCurve(Vector3 startPoint, Vector3 endPoint, Vector3 middlePoint1, Vector3 middlePoint2)
        {
            List<Vector3> allPoints = new List<Vector3>();
    
            for (int i = 0; i < nultiple; i++)
            {
                float tempPercent = (float)i / (float)nultiple;
                float dis1 = Vector3.Distance(startPoint, middlePoint1);
                Vector3 pointL1 = startPoint + Vector3.Normalize(middlePoint1 - startPoint) * dis1 * tempPercent;
                float dis2 = Vector3.Distance(middlePoint1, middlePoint2);
                Vector3 pointL2 = middlePoint1 + Vector3.Normalize(middlePoint2 - middlePoint1) * dis2 * tempPercent;
                float dis3 = Vector3.Distance(pointL1, pointL2);
                Vector3 pointLeft = pointL1 + Vector3.Normalize(pointL2 - pointL1) * dis3 * tempPercent;
    
                float dis4 = Vector3.Distance(middlePoint2, endPoint);
                Vector3 pointR1 = middlePoint2 + Vector3.Normalize(endPoint - middlePoint2) * dis4 * tempPercent;
                float dis5 = Vector3.Distance(pointL2, pointR1);
                Vector3 pointRight = pointL2 + Vector3.Normalize(pointR1 - pointL2) * dis5 * tempPercent;
    
                float disLeftAndRight = Vector3.Distance(pointLeft, pointRight);
                Vector3 linePoint = pointLeft + Vector3.Normalize(pointRight - pointLeft) * disLeftAndRight * tempPercent;
                allPoints.Add(linePoint);
            }
            allPoints.Add(endPoint);
            return allPoints;
        }
    
    }

     场景中新建一个image,删掉image组件,挂上上面的代码即可。

    调用的代码如下:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.EventSystems;
    
    public class DragNode : MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
    {
        DrawLine drawLine;
        GameObject lineGo;
    
        public void OnPointerDown(PointerEventData eventData)
        {
            lineGo = new GameObject("Line");
            lineGo.transform.SetParent(transform);
            drawLine = lineGo.AddComponent<DrawLine>();
            drawLine.startPos = Input.mousePosition;
            drawLine.endPos = Input.mousePosition;
            drawLine.color = Color.red;
        }
    
        public void OnDrag(PointerEventData eventData)
        {
            drawLine.endPos = Input.mousePosition;
        }
    
        public void OnPointerUp(PointerEventData eventData)
        {
            DestroyImmediate(lineGo);
        }
    }

     

    补充:如果需要遮罩,加一句代码即可:

    [RequireComponent(typeof(CanvasRenderer))]
    public class DrawLine : MaskableGraphic, ICanvasRaycastFilter

     

    就这样。拜拜~

  • 相关阅读:
    yii AR 模式操作
    sql 注入命令大全
    PHP 防xss攻击
    yii rbac管理
    yii2.0 表单小部件常用的默认选中
    yii 表单小部件使用
    多个API接口
    iwebshop 增删改查
    搜索引擎接口
    2003终端服务器授权,120天试用期限制
  • 原文地址:https://www.cnblogs.com/yzxhz/p/15673404.html
Copyright © 2011-2022 走看看