Unity 自定义圆形图片

使用Mask组件的缺点

我们知道项目中可以这样展示圆形图片,一般是Image组件,下面再加上一个圆形Mask。但是这样做有几个缺点:

  1. 使用Mask会额外消耗多一个Drawcall来创建Mask,做像素剔除。
  2. Mask不利于层级合并。原本同一图集里的ui可以合并层级,仅需一个Drawcall渲染,如果加入Mask,就会将一个ui整体分割成了Mask下的子ui与其他ui,两者只能各自进行层级合并,至少要两个Drawcall。Mask用得多了,一个ui整体会被分割得四分五裂,就会严重影响层次合并的效率了。
  3. 产生的圆形不够完美,边上有锯齿现象。

自定义圆形图片

我们使用自己定义的圆形图片,不仅只有一个DrawCall,还能和同一图集的UI进行合并,产生的圆形程度也能自己控制。上面的所有缺点都没有了。所以我们要自定义圆形图片。

继承Image类

我们可以继承Image类,然后重写OnPopulateMesh方法,重新写入圆形形状的渲染顶点,三角面片信息,从而实现我们自己的圆形Image。

圆形可以由若干个以圆形为顶点的等腰三角形片组成正多边形,三角形多了,就近似圆形。所以首先定义一个参数圆形由多少个三角形组成。

然后我们添加顶点信息,首先添加圆心为顶点的信息,大致伪代码是这样的:

public class CircleImage : Image
{
    public int segements = 50;

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        //清除原来画的信息
        vh.Clear();

        UIVertex origin = new UIVertex();  //原点
        origin.color = color;
        //计算顶点vertexPos
        origin.position = vertexPos;
        //计算uv0
        origin.uv0 =uv0Pos ;
        vh.AddVert(origin);
    }
}

我们需要计算一个点的position和uv0坐标。原点的position就是(0,0),在圆上的点的坐标就是(半径Cos弧度,半径SIn弧度),半径我们知道,角度可以通过(2pi/三角形个数)求出。

而Uv的值,首先我们可以通过DataUtility.GetOuterUV,得到一个vector4的对象,这个对象的x,y,z,w表示一个矩形,左下,右下,左上,右上。我们的Position的值是可以知道的。因此我们可以通过uv和position的比值来求出对应的uv值,在原点的position是(0,0)。对应的uv值就是(原点position.x比率+uv中心点.x,原点y比率+uv中心点.y)。注意这里是添加要分的分数+1个顶点,有要分的份上个三角形。然后把这些顶点,三角形都添加到VertexHelper 的实例类中。

   Vector4 uv4 = overrideSprite == null ? Vector4.zero : DataUtility.GetOuterUV(overrideSprite);

        float width = rectTransform.rect.width;
        float height = rectTransform.rect.height;

        float uvWidth = uv4.z - uv4.x;
        float uvHeight = uv4.w - uv4.y;

        Vector2 uvCenter  = new Vector2(uvWidth/2,uvHeight/2);

        Vector2 ratio = new Vector2(uvWidth/width,uvHeight/height);

这是计算position和uv的宽高,得出uv和position的比率。

   UIVertex origin = new UIVertex();
        origin.color = color;
        origin.position = Vector3.zero;
        origin.uv0  = new Vector2(origin.position.x*ratio.x+uvCenter.x,origin.position.y*ratio.y+uvCenter.y);
        vh.AddVert(origin);

        float angle = 2 * Mathf.PI / segements;
        float curAngle = 0;
        float radius = width / 2;
        for (int i = 0; i < segements+1; i++)
        {
            float x = radius * Mathf.Cos(curAngle);
            float y = radius * Mathf.Sin(curAngle);
            curAngle += angle;

            UIVertex originTemp = new UIVertex();
            originTemp.color = color;
            originTemp.position = new Vector2(x,y);
            originTemp.uv0 = new Vector2(originTemp.position.x * ratio.x + uvCenter.x, originTemp.position.y * ratio.y + uvCenter.y);
            vh.AddVert(originTemp);
        }

        for (int i = 0; i < segements; i++)
        {
            vh.AddTriangle(i+1,0,i+2);
        }

这是把顶点和三角形都添加到vh类里面,Image会自动去绘制图形。

就这样,我们就完成了简单的圆形Image制作,但是还是有很多缺陷,例如无法实现精确点击,无法使用锚点,没有暴露参数。但是只有你一步一步修改,就能完成达到你想要的效果,我这里不仅完善了以上的缺陷,还增加了可以按照百分比显示遮罩。想要的童鞋可以参考我的源码。

实现效果:
在这里插入图片描述

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Sprites;
using UnityEngine.UI;


/// <summary>
///1.为什么不用Mask组件
///1.1 增加Drawcall
///1.2 边上有锯齿
///
///2.Position和uv的关系
/// 以rect的中心点为中心,Position左下,右下,左上,右上可以表示为(-200,-200)(200,-200)(-200,200)(200,200)
/// 以左下为中心,Uv可以表示为四个点距离左下的位置(0,0,1,1)
/// uv换算成Position就是让uv填满整个rect   uv=position*(uv/position)
/// </summary>
public class CircleImage : Image
{
    //圆形由多少块三角形组成
    [SerializeField]
    public int segements=60;

    [SerializeField]
    public float showPercent;

    //半径
    private float radius;
    private readonly Color32 GRAY_COLOR = new Color32(60, 60, 60, 255);
    private List<Vector3> _vertexList;

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();
        _vertexList = new List<Vector3>();
        int realsegments = (int) (segements * showPercent);

        //rect的width 和 height
        float width = rectTransform.rect.width;
        float height = rectTransform.rect.height;

        radius = width > height ? height / 2 : width / 2;

        //获取当前UV信息
        Vector4 uv = Vector4.zero;
        uv = DataUtility.GetOuterUV(overrideSprite);
        //uv为什么是4维变量  对应着离原点的距离x ,y,以及x+width,y+height;

        float uvWidth = uv.z - uv.x;
        float uvHight = uv.w - uv.y;

        
        Vector2 uvCenter = new Vector2(uvWidth / 2, uvHight / 2);

        //uv坐标和rect之间的换算比例
        Vector2 converRatio = new Vector2(uvWidth / width, uvHight / height);


        //求出一个小三角形的弧度
        float radian = (2 * Mathf.PI) / segements;

        Vector2 vertexPos = new Vector2((0.5f - rectTransform.pivot.x) * width, (0.5f - rectTransform.pivot.y) * height);

        //求出一个小三角形的三个顶点
        //通过传入点的顺序 判断是正面(顺时针)还是背面  背面不渲染
        UIVertex origin = new UIVertex();  //原点
        origin.color = GetOriginColor();
        Vector2 uvrela= Vector2.zero;
        origin.uv0 = new Vector2(uvrela.x * converRatio.x+uvCenter.x, uvrela.y * converRatio.y+uvCenter.y);
        origin.position = vertexPos;
        vh.AddVert(origin);
        //需要的顶点数等于块数+1
        int vertexCount = realsegments + 1;
        float curRadian = 0;
        for (int i = 0; i < segements+1; i++)
        {
            float x = Mathf.Cos(curRadian)* radius;
            float y = Mathf.Sin(curRadian)* radius;
            curRadian += radian;
            UIVertex vertexTemp = new UIVertex();  //原点
            vertexTemp.color = i < vertexCount ?  new Color32(60, 60, 60, 255):(Color32)color;
            if (realsegments==0)
            {
                vertexTemp.color = color;
            }
            uvrela = new Vector2(x,y);
            vertexTemp.uv0 = new Vector2(uvrela.x * converRatio.x + uvCenter.x, uvrela.y * converRatio.y + uvCenter.y);
            vertexTemp.position = new Vector2(x,y)+vertexPos;
            vh.AddVert(vertexTemp);
            _vertexList.Add(vertexTemp.position);
        }

        int id = 1;
        //生成面片生成UV
        for (int i = 0; i < segements; i++)
        {
            //id按照传入三角形的顺序生成
            vh.AddTriangle(id,0,id+1);
            id++;
        }
    }


    private Color32 GetOriginColor()
    {
        //随着showPercent从0变到1 ,color从白变成灰
        Color32 colorTemp = (Color.white - GRAY_COLOR) * showPercent;
        return new Color32((byte) (255 - colorTemp.r),
            (byte)(255- colorTemp.g),
            (byte)(255 - colorTemp.b),
            255);
    }


    public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
    {
        Vector2 localPoint;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera,
            out localPoint);
        return IsValid(localPoint);
    }

    private bool IsValid(Vector2 localPoint)
    {
        return GetCrossPointNum(localPoint, _vertexList) %2 ==1;

    }

    private int GetCrossPointNum(Vector2 localPoint,List<Vector3> vertexList)
    {
        int count = 0;
        Vector3 vert1 = Vector3.zero;
        Vector3 vert2 = Vector3.zero;
        int vertCount = vertexList.Count;
        for (int i = 0; i < vertCount; i++)
        {
            vert1 = vertexList[i];
            vert2 = vertexList[(i + 1) % vertCount];
            if (IsYInRang(localPoint,vert1,vert2))
            {

                if (localPoint.x<GetX(vert1,vert2,localPoint.y))
                {
                    count++;
                }
            }
        }
        return count;
    }

    /// <summary>
    ///获取相交点的X坐标
    /// </summary>
    /// <param name="vert1"></param>
    /// <param name="vert2"></param>
    /// <param name="localPointY"></param>
    /// <returns></returns>
    private float GetX(Vector3 vert1, Vector3 vert2, float localPointY)
    {

        float k = (vert1.y - vert2.y) / (vert1.x - vert2.x);
        return vert1.x + (localPointY - vert1.y) / k;
    }

    private bool IsYInRang(Vector2 localPoint, Vector3 vert1, Vector3 vert2)
    {
        if (vert1.y>vert2.y)
        {
            return vert1.y > localPoint.y && localPoint.y > vert2.y;
        }
        else
        {
            return vert2.y>localPoint.y && localPoint.y>vert1.y;
        }
        
    }
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Unity中的自定义弹框可以使用Unity3D Custom Popup插件来实现。该插件可以推迟构造弹出窗口的显示,对于构造弹出窗口相对昂贵的情况非常有用。你可以通过执行`yarn add rotorz/unity3d-custom-popup`命令来安装该软件包,并将其与Unity工具兼容。然后按照插件的说明进行操作,将自定义弹出控件添加到Unity编辑器界面中。 具体而言,你可以在代码中调用`MyPermissionDialog.Builder`来创建一个自定义弹框。可以使用`setPositiveButton`方法设置弹框的确认按钮的点击事件,并在点击事件中执行相应的操作。通过`setTitleAndContext`方法可以设置弹框的标题和内容。最后,通过调用`create`方法创建弹框,并使用`show`方法显示出来。 请注意,这只是一种实现自定义弹框的方式,根据你的具体需求,可能还有其他的方法可供选择。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Android自定义弹框以及弹框资源加载](https://blog.csdn.net/u014146238/article/details/104824842)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [unity3d-custom-popup:Unity编辑器界面的自定义弹出控件,将弹出菜单的构造推迟到显示为止。 这对于弹出...](https://download.csdn.net/download/weixin_42131443/18312293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KindSuper_liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值