Unity实现圆形Image组件

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

一、前言

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

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

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

二、实现自己的圆形组件

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

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

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

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++;
            }
        }
    }
}

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

(0)

相关推荐

  • Unity UGUI实现卡片椭圆方向滚动

    本文实例为大家分享了UGUI实现卡片椭圆方向滚动的具体代码,供大家参考,具体内容如下 搭建简单的场景 运行效果 卡片移动动画通过插件DoTween实现 控制脚本: using UnityEngine; using System.Collections; using UnityEngine.UI; using DG.Tweening; public class CardMove : MonoBehaviour { GameObject[] sprites; int halfSize; Vector

  • Unity3d 使用Gizmos画一个圆圈

    Gizmos是场景视图里的一个可视化调试工具. 在做项目过程中.我们常常会用到它,比如:绘制一条射线等. Unity3D 4.2版本号截至.眼下仅仅提供了绘制射线,线段,网格球体,实体球体,网格立方体,实体立方体,图标.GUI纹理,以及摄像机线框. 假设须要绘制一个圆环还须要自己写代码 using UnityEngine; using System; public class HeGizmosCircle : MonoBehaviour { public Transform m_Transfor

  • Unity实现圆形Image组件

    本文实例为大家分享了Unity实现圆形Image组件的具体代码,供大家参考,具体内容如下 一.前言 游戏里很多图片都是以圆形展示的,例如头像.技能图标等,一般做法是使用Image组件+Mask组件实现,但是Mask组件会影响效率(增加额外的drawcall)所以不建议大量使用 UGUI的Mask实现原理:利用GPU的模版缓冲 Mask组件会赋给父级和子级UI一个特殊的材质,这个材质会给Image的每个像素点进行标记并放在一个称为Stencil Buffer的缓存内,父级每个像素点的标记设置为1,

  • 基于vue实现圆形菜单栏组件

    整个样式都是基于css3 得transform而实现得. 每个扇形角度为360/12=30deg,当然,你不想做圆形也可以,公式就是     扇形角度=你想绘制得角度/扇形个数 当你计算好每个扇形得角度时,需要将li元素倾斜,倾斜角度=90-扇形面积,我的这个倾斜角度就是90-30=60deg,然后使用css3 得skew() circle-panel-1 circle-panel-2 circle-panel-3 当每个扇形倾斜60deg之后,会在原来得位置上,要想每个扇形有规律得组合在一起,

  • 鸿蒙HarmonyOS App开发造轮子之自定义圆形图片组件的实例代码

    一.背景 在采用Java配合xml布局编写鸿蒙app页面的时候,发现sdk自带的Image组件并不能将图片设置成圆形,反复了翻阅了官方API手册(主要查阅了Compont和Image相关的API),起初发现了一个setCornerRadius方法,于是想着将图片宽度和高度设置为一样,然后调用该方法将radios设置为宽度或者高度的一半,以为可以实现圆形图片的效果,后来发现不行.于是乎想着能不能通过继承原有的Image自己来动手重新自定义一个支持圆形的图片组件. 二.思路: 1.对比之前自己在其他

  • 一篇文章教会你用Unity制作网格地图生成组件

    目录 前言 1,创建组建出网格的基本单元 2,编辑网格创建脚本 3,地图生成案例 总结 前言 如果你玩过三国志这种类型的战旗游戏或者模拟城市.部落冲突.海岛奇兵这种模拟经营类的游戏,那么你对网格地图一定不会陌生.在这些游戏中,所有地图场景中的物体都是基于整齐的网格来记录位置等信息.如下图: 如果你还是感知不到什么是网格地图.俄罗斯方块或者贪吃蛇你一定不会陌生,物体的存在是依托于规整的网格地图而存在的. 还是一如既往,本篇文章为零基础小白文,如果你是小萌新,并且对网格地图感兴趣的话,可以学习本片文

  • unity 如何获取Text组件里text内容的长度

    我就废话不多说了,大家还是直接看代码吧~ /// <summary> /// 计算字符串在指定text控件中的长度 /// </summary> /// <param name="message"></param> /// <returns></returns> int CalculateLengthOfText(string message,Text tex) { int totalLength = 0; Fon

  • Unity 实现给物体动态添加事件

    介绍一个方法给物体动态的添加事件(点击 拖拽等) using System.Events; using System.EventSystems; //_go is the gameobject which need add event //_go 是物体,因为EventTrigger是unity中的一个组件,我们要用添加组件的方式来给物体添加事件触发组件 //_type is the EventTriggerType(point , drag , clickdown...... //_type

  • unity AudioSource播放完声音后要执行的函数或条件操作

    将脚本挂在要判断声音是否播放完毕的物体上 using System.Collections; using UnityEngine; using UnityEngine.Events; [RequireComponent(typeof(AudioSource))] public class AudioManager : MonoBehaviour { public static AudioManager instence = null; private AudioSource _audio; vo

  • Unity中 mesh生成斜坡的示例代码

    Mesh概念: Mesh是Unity中的一个组件,称为网格组件.通俗的讲,Mesh是指模型的网格,3D模型是由多边形拼接而成,而多边形实际上是由多个三角形拼接而成的.所以一个3D模型的表面其实是由多个彼此相连的三角面构成.三维空间中,构成这些三角形的点和边的集合就是Mesh. Mesh组成: 1.顶点坐标数组vertexes 2.顶点在uv坐标系中的位置信息数组uvs 3.三角形顶点顺时针或者逆时针索引数组triangles 4.MeshFiler组件,用于增加mesh属性 5.MeshRend

  • Unity ScrollView实现无限循环效果

    本文实例为大家分享了Unity ScrollView实现无限循环效果的具体代码,供大家参考,具体内容如下 在Unity引擎中ScrollView组件是一个使用率比较高的组件,该组件能上下或者左右拖动的UI列表,背包.展示多个按钮等情况的时候会用到,在做排行榜类似界面时,item非常多,可能有几百个,一次创建这么多GameObject是非常卡的.为此,使用只创建可视区一共显示的个数,加上后置准备个数. 由于ScrollView有两种滚动方式,水平滚动或者垂直滚动,所以我创建了ScrollBase基

  • Unity使用多态制作计算器功能

    本文实例为大家分享了Unity使用多态制作计算器,供大家参考,具体内容如下 Unity中需要的组件 在Unity中创建两个InputField,一个Dropdown,一个Button和一个Text 创建脚本文件 计算父类 using System.Collections; using System.Collections.Generic; using UnityEngine; public class Jsq : MonoBehaviour { public abstract class Cal

随机推荐