zoukankan      html  css  js  c++  java
  • UGUI圆形UI与多边形点击范围

    1.前言

    Unity默认的ugui mesh是四边形网格,若要实现圆形ui可以使用mask实现,此处提供一种更改mesh的方法,并将点击范围控制在圆形范围内。(从易用性角度并不会方便太多)
    同时提供一种在不使用mask的情况下实现复杂点击范围。但此上两种方案均需要判断点击点是否在一个多边形内(圆形按多边形处理)

    2.RayCrossing

    若判断一个点p是否在一个多边形内,可以使用rayCrossing方法。即点p发出一条射线,与多边形相交,假若交点个数是奇数,说明点p落在多边形内,交点个数为偶数说明点p在多边形外。以点p为端点,水平方向右侧发出的射线来与多边形相交判断,那么顶点v1,v2组成的线段与射线若有交点q,则点q必定满足两个条件:

            v2.y < q.y = p.y > v1.y
            p.x < q.x
    

    方法如下:
    如果Contains返回true则包含此点,否则不包含

        private bool Contains(Vector2 p, Vector3[] outterVertices)
        {
            var crossNumber = 0;
            RayCrossing(p, outterVertices, ref crossNumber);//检测内环
            //RayCrossing(p, outterVertices, ref crossNumber);//检测外环
            return (crossNumber & 1) == 1;
        }
    
        private void RayCrossing(Vector2 p, Vector3[] vertices, ref int crossNumber)
        {
            for (int i = 0, count = vertices.Length; i < count; i++)
            {
                var v1 = vertices[i];
                var v2 = vertices[(i + 1) % count];
    
                if (((v1.y <= p.y) && (v2.y > p.y))
                    || ((v1.y > p.y) && (v2.y <= p.y)))
                {
                    if (p.x < v1.x + (p.y - v1.y) / (v2.y - v1.y) * (v2.x - v1.x))
                    {
                        crossNumber += 1;
                    }
                }
            }
        }
    

    3.圆形UI

    示例代码从mesh角度生成圆形mesh,并根据2.0方法将点击范围控制在圆形范围内。示例代码继承RawImage,也可以改成Image或者Graphic或者MaskableGraphic.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.EventSystems;
    using UnityEngine.UI;
    
    public class CircleUI : RawImage,IPointerDownHandler,ICanvasRaycastFilter
    {
        Vector3[] vertices;
        int[] triangles;
        Vector2[] uvs;
    
        bool initialed = false;
        float currentRadius = 0;
    
    
        protected override void OnPopulateMesh(VertexHelper vh)
        {
            vh.Clear();
    
            var size = GetPixelAdjustedRect();
            float radius = (size.width > size.height ? size.height : size.width) / 2;
    
            if(!initialed || currentRadius != radius)
            {
                vertices = GetVertices(radius);
                triangles = GetTriangles();
                uvs = GetUvs();
            }
    
            for (int i = 0; i <= triCount; i++)
            {
                vh.AddVert(vertices[i], Color.white, uvs[i]);
    
                if (i < triCount)
                {
                    vh.AddTriangle(triangles[i * 3], triangles[i * 3 + 1], triangles[i * 3 + 2]);
                }
            }
        }
    
        int triCount = 20;
    
        protected Vector3[] GetVertices(float radius)
        {
            Vector3 []
            vertices = new Vector3[triCount + 1];
                vertices[0] = Vector3.zero;
                float angleDelta = 2 * Mathf.PI / triCount;
    
                for (int i = 0; i<triCount; i++)
                {
                    float angle = angleDelta * i;
        float x = radius * Mathf.Cos(angle);
        float y = radius * Mathf.Sin(angle);
    
        vertices[i + 1] = new Vector3(x, y, 0);
    }
    
                return vertices;
        }
    
        protected int[] GetTriangles()
        {
                int[] triangles = new int[triCount * 3];
    
                for (int i = 0; i<triCount; i++)
                {
    
                    triangles[i * 3] = 0;
                    triangles[i * 3 + 2] = i + 1;
    
                    if (i + 2 > triCount)
                    {
                        triangles[i * 3 + 1] = 1;
                    }
                    else
                    {
                        triangles[i * 3 + 1] = i + 2;
                    }
                }
                return triangles;
        }
    
        protected Vector2[] GetUvs()
        {
            Vector2[] uvs = new Vector2[triCount + 1];
            uvs[0] = new Vector2(0.5f, 0.5f);
            float angleDelta = 2 * Mathf.PI / triCount;
    
            for (int i = 0; i < triCount; i++)
            {
                float angle = angleDelta * i;
                float x = Mathf.Cos(angle) * 0.5f + 0.5f;
                float y = Mathf.Sin(angle) * 0.5f + 0.5f;
    
                uvs[i + 1] = new Vector2(x, y);
            }
            return uvs;
        }
    
        public void OnPointerDown(PointerEventData eventData)
        {
            Debug.Log("OnPointerDown");
        }
    
        public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
        {
    
            Vector2 local;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, sp, eventCamera, out local);
            return Contains(local, vertices);
        }
    
        private bool Contains(Vector2 p, Vector3[] outterVertices)
        {
            var crossNumber = 0;
            RayCrossing(p, outterVertices, ref crossNumber);//检测内环
            //RayCrossing(p, outterVertices, ref crossNumber);//检测外环
            return (crossNumber & 1) == 1;
        }
    
        private void RayCrossing(Vector2 p, Vector3[] vertices, ref int crossNumber)
        {
            for (int i = 0, count = vertices.Length; i < count; i++)
            {
                var v1 = vertices[i];
                var v2 = vertices[(i + 1) % count];
    
                if (((v1.y <= p.y) && (v2.y > p.y))
                    || ((v1.y > p.y) && (v2.y <= p.y)))
                {
                    if (p.x < v1.x + (p.y - v1.y) / (v2.y - v1.y) * (v2.x - v1.x))
                    {
                        crossNumber += 1;
                    }
                }
            }
        }
    }
    
    

    4.多边形点击范围

    此示例通过PolygonCollider2D 来实现多边形区域。在需要点击的ui上挂在如下脚本,并挂在PolygonCollider2D 组件,编辑此组件形状以达到需要的点击范围,然后可以通过其points属性获取到点击区域,继而通过判断点是否在点击范围内实现区域点击。由于PolygonCollider2D 组件只是获取点击范围(所以可以修改下面脚本的逻辑,将可点击多边形范围的points的值缓存下来,然后destroy掉PolygonCollider2D 组件)

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class PolygonClick : RawImage,ICanvasRaycastFilter
    {
        PolygonCollider2D polygon;
    
        public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
        {
            Vector2 local;
            RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, sp, eventCamera, out local);
            return Contains(local, polygon.points);
        }
    
        protected override void Start()
        {
            polygon = GetComponent<PolygonCollider2D>();
        }
    
    
        private bool Contains(Vector2 p, Vector2[] outterVertices)
        {
            var crossNumber = 0;
            RayCrossing(p, outterVertices, ref crossNumber);
            return (crossNumber & 1) == 1;
        }
    
        private void RayCrossing(Vector2 p, Vector2[] vertices, ref int crossNumber)
        {
            for (int i = 0, count = vertices.Length; i < count; i++)
            {
                var v1 = vertices[i];
                var v2 = vertices[(i + 1) % count];
    
                if (((v1.y <= p.y) && (v2.y > p.y))
                    || ((v1.y > p.y) && (v2.y <= p.y)))
                {
                    if (p.x < v1.x + (p.y - v1.y) / (v2.y - v1.y) * (v2.x - v1.x))
                    {
                        crossNumber += 1;
                    }
                }
            }
        }
    }
    
    

    5.结论

  • 相关阅读:
    python之json&pickle
    python之装饰器
    软件测试基础
    软件测试分类
    python3文件的读写操作
    python3对excel文件读写操作
    Java集合整理
    mybatis一对多关系的关联查询
    用xftp从win7系统传输一些必要的文件到Linux
    Spring和Mybatis的整合
  • 原文地址:https://www.cnblogs.com/llstart-new0201/p/14603940.html
Copyright © 2011-2022 走看看