服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - Unity实现圆形Image组件

Unity实现圆形Image组件

2022-12-16 13:23Hello Bug. C#

这篇文章主要为大家详细介绍了Unity实现圆形Image组件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文为大家分享了Unity实现圆形Image组件的具体代码,供大家参考。

一、前言

游戏里很多图片都是以圆形展示的,例如头像、技能图标等,一般做法是使用Image组件+Mask组件实现,但是Mask组件会影响效率(增加额外的drawcall)所以不建议大量使用

UGUI的Mask实现原理:利用GPU的模版缓冲

Mask组件会赋给父级和子级UI一个特殊的材质,这个材质会给Image的每个像素点进行标记并放在一个称为Stencil Buffer的缓存内,父级每个像素点的标记设置为1,子级UI进行渲染的时候会去检查这个Stencil Buffer内的标记是否为1,如果为1则进行渲染,否则不渲染

Unity实现圆形Image组件

Unity实现圆形Image组件

Unity实现圆形Image组件

 

二、实现自己的圆形组件

像Image,RawImage这些组件都是继承自自MsakGraphics类,MsakGraphics类继承自Graphic类,Graphic类中有个OnPopulateMesh方法用于绘制图形,UGUI的Image组件实现原理是重写了OnPopulateMesh方法并绘制了一个矩形,所以按照这个思路我们可以重写OnPopulateMesh方法直接绘制一个圆形
——获取图片的长宽、uv等信息

Unity实现圆形Image组件

——OnPopulateMesh:当UI元素生成顶点数据时会调用OnPopulateMesh(VertexHelper vh)函数,我们只需要将原先的矩形顶点数据清除,改写入圆形顶点数据,这样渲染出来的自然是圆形图片

——不规则UI元素的响应区域判定
UI组件的响应区域判定是通过实现ICanvasRaycastFilter接口中的IsRaycastLocationValid函数,它的返回值是一个bool值,返回true则视为可以响应,例如Image组件,它判定了两个条件:当前屏幕坐标是否在当前图片矩形区域内和当前屏幕坐标的图片区域透明度是否大于alphaHitTestMinimumThreshold参数
我们想实现精确的点击判断,可以代码动态将alphaHitTestMinimumThreshold参数设置为0.1,这样就实现了只有在透明度大于0.1的像素点才视为响应,但它要求图片的Read/Write Enabled必须开启,这就导致了图片占用了两份内存,所以不建议使用
对于像素级的点击判定,有一种算法可以实现:Ray-Crossing算法
此算法适用于所有图形,实现思路是从指定点向任意方向发出一条水平射线,与图形相交,如果交点是奇数个,则点在图形内,如果交点是偶数个,则点在图形外

Unity实现圆形Image组件

using UnityEngine;
using UnityEngine.Sprites;
using UnityEngine.UI;
using System.Collections.Generic;
 
/// <summary>
/// 圆形Image组件
/// </summary>
[AddComponentMenu("LFramework/UI/CircleImage", 11)]
public class CircleImage : MaskableGraphic, ICanvasRaycastFilter
{
    /// <summary>
    /// 渲染类型
    /// </summary>
    public enum RenderType
    {
        Simple,
        Filled,
    }
 
    /// <summary>
    /// 填充类型
    /// </summary>
    public enum FilledType
    {
        Radial360,
    }
 
    /// <summary>
    /// 绘制起始点(填充类型-360度)
    /// </summary>
    public enum Origin360
    {
        Right,
        Top,
        Left,
        Bottom,
    }
 
    //Sprite图片
    [SerializeField]
    Sprite m_Sprite;
    public Sprite Sprite
    {
        get { return m_Sprite; }
    }
 
    //贴图
    public override Texture mainTexture
    {
        get
        {
            if (m_Sprite == null)
            {
                if (material != null && material.mainTexture != null)
                {
                    return material.mainTexture;
                }
                return s_WhiteTexture;
            }
 
            return m_Sprite.texture;
        }
    }
 
    //渲染类型
    [SerializeField]
    RenderType m_RenderType;
 
    //填充类型
    [SerializeField]
    FilledType m_FilledType;
 
    //绘制起始点(填充类型-360度)
    [SerializeField]
    Origin360 m_Origin360;
 
    //是否为顺时针绘制
    [SerializeField]
    bool m_Clockwise;
 
    //填充度
    [SerializeField]
    [Range(0, 1)]
    float m_FillAmount;
 
    //多少个三角面组成
    [SerializeField]
    int segements = 100;
 
    List<Vector3> vertexCache = new List<Vector3>();
 
    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();
        vertexCache.Clear();
 
        switch (m_RenderType)
        {
            case RenderType.Simple:
                GenerateSimpleSprite(vh);
                break;
            case RenderType.Filled:
                GenerateFilledSprite(vh);
                break;
        }
    }
 
    void GenerateSimpleSprite(VertexHelper vh)
    {
        Vector4 uv = m_Sprite == null
            ? Vector4.zero
            : DataUtility.GetOuterUV(m_Sprite);
        float uvWidth = uv.z - uv.x;
        float uvHeight = uv.w - uv.y;
        float width = rectTransform.rect.width;
        float height = rectTransform.rect.height;
        float dia = width > height ? width : height;
        float r = dia * 0.5f;
        Vector2 uvCenter = new Vector2((uv.x + uv.z) * 0.5f, (uv.y + uv.w) * 0.5f);
        Vector3 posCenter = new Vector2((0.5f - rectTransform.pivot.x) * width, (0.5f - rectTransform.pivot.y) * height);
        float uvScaleX = uvWidth / width;
        float uvScaleY = uvHeight / height;
        float deltaRad = 2 * Mathf.PI / segements;
 
        float curRad = 0;
        int vertexCount = segements + 1;
        vh.AddVert(posCenter, color, uvCenter);
        for (int i = 0; i < vertexCount - 1; i++)
        {
            UIVertex vertex = new UIVertex();
            Vector3 posOffset = new Vector3(r * Mathf.Cos(curRad), r * Mathf.Sin(curRad));
            vertex.position = posCenter + posOffset;
            vertex.color = color;
            vertex.uv0 = new Vector2(uvCenter.x + posOffset.x * uvScaleX, uvCenter.y + posOffset.y * uvScaleY);
            vh.AddVert(vertex);
            vertexCache.Add(vertex.position);
 
            curRad += deltaRad;
        }
 
        for (int i = 0; i < vertexCount - 2; i++)
        {
            vh.AddTriangle(0, i + 1, i + 2);
        }
        vh.AddTriangle(0, segements, 1);
    }
 
    void GenerateFilledSprite(VertexHelper vh)
    {
        Vector4 uv = m_Sprite == null
            ? Vector4.zero
            : DataUtility.GetOuterUV(m_Sprite);
        float uvWidth = uv.z - uv.x;
        float uvHeight = uv.w - uv.y;
        float width = rectTransform.rect.width;
        float height = rectTransform.rect.height;
        float dia = width > height ? width : height;
        float r = dia * 0.5f;
        Vector2 uvCenter = new Vector2((uv.x + uv.z) * 0.5f, (uv.y + uv.w) * 0.5f);
        Vector3 posCenter = new Vector2((0.5f - rectTransform.pivot.x) * width, (0.5f - rectTransform.pivot.y) * height);
        float uvScaleX = uvWidth / width;
        float uvScaleY = uvHeight / height;
        float deltaRad = 2 * Mathf.PI / segements;
 
        switch (m_FilledType)
        {
            case FilledType.Radial360:
                float quarterRad = 2 * Mathf.PI * 0.25f;
                float curRad = quarterRad * (int)m_Origin360;
                int vertexCount = m_FillAmount == 1
                    ? segements + 1
                    : Mathf.RoundToInt(segements * m_FillAmount) + 2;
                vh.AddVert(posCenter, color, uvCenter);
                for (int i = 0; i < vertexCount - 1; i++)
                {
                    UIVertex vertex = new UIVertex();
                    Vector3 posOffset = new Vector3(r * Mathf.Cos(curRad), r * Mathf.Sin(curRad));
                    vertex.position = posCenter + posOffset;
                    vertex.color = color;
                    vertex.uv0 = new Vector2(uvCenter.x + posOffset.x * uvScaleX, uvCenter.y + posOffset.y * uvScaleY);
                    vh.AddVert(vertex);
                    vertexCache.Add(vertex.position);
 
                    curRad += m_Clockwise ? -deltaRad : deltaRad;
                }
 
                for (int i = 0; i < vertexCount - 2; i++)
                {
                    vh.AddTriangle(0, i + 1, i + 2);
                }
                if (m_FillAmount == 1)
                {
                    vh.AddTriangle(0, segements, 1);
                }
                break;
        }
    }
 
    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
    {
        Vector2 localPos;
        int crossPointCount;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, sp, eventCamera, out localPos);
        RayCrossing(localPos, out crossPointCount);
        return crossPointCount % 2 != 0;
    }
 
    public void RayCrossing(Vector2 localPos, out int crossPointCount)
    {
        crossPointCount = 0;
        for (int i = 0; i < vertexCache.Count; i++)
        {
            Vector3 p1 = vertexCache[i];
            Vector3 p2 = vertexCache[(i + 1) % vertexCache.Count];
 
            if (p1.y == p2.y) continue;
            if (localPos.y <= Mathf.Min(p1.y, p2.y)) continue;
            if (localPos.y >= Mathf.Max(p1.y, p2.y)) continue;
            float crossX = (localPos.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
            if (crossX >= localPos.x)
            {
                crossPointCount++;
            }
        }
    }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/LLLLL__/article/details/121737217

延伸 · 阅读

精彩推荐
  • C#Unity实现跑马灯抽奖效果

    Unity实现跑马灯抽奖效果

    这篇文章主要为大家详细介绍了Unity实现跑马灯抽奖效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Sweet_james7422022-03-10
  • C#unity实现车方向盘转动效果

    unity实现车方向盘转动效果

    这篇文章主要为大家详细介绍了unity实现车方向盘转动效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    贪玩的孩纸时代8542022-09-05
  • C#c#调用c语言dll需要注意的地方

    c#调用c语言dll需要注意的地方

    这篇文章主要介绍了c#调用c语言dll需要注意的地方,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下...

    weareu9752022-11-08
  • C#C#实现骑士飞行棋

    C#实现骑士飞行棋

    这篇文章主要为大家详细介绍了C#实现骑士飞行棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    「已注销」4982022-08-24
  • C#C#.NET实现网页自动登录的方法

    C#.NET实现网页自动登录的方法

    这篇文章主要介绍了C#.NET实现网页自动登录的方法,以实例形式分析了C#实现点击自动登录的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...

    清清飞扬11842021-10-28
  • C#C#多态的三种实现方式(小结)

    C#多态的三种实现方式(小结)

    这篇文章主要介绍了C#多态的三种实现方式(小结),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面...

    令狐掌门7572022-11-09
  • C#C#实现打造气泡屏幕保护效果

    C#实现打造气泡屏幕保护效果

    本文是介给大家介绍一个很好玩的小程序:气泡屏幕保护!类似于windows的屏保功能,有需要的朋友可以参考一下。...

    李sir4382021-12-08
  • C#深入理解StringBuilder的使用方法

    深入理解StringBuilder的使用方法

    下面小编就为大家带来一篇深入理解StringBuilder的使用方法。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    编程之家5602021-11-22