溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Unity如何實(shí)現(xiàn)圓形Image組件

發(fā)布時(shí)間:2022-01-07 15:36:16 來(lái)源:億速云 閱讀:143 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本文小編為大家詳細(xì)介紹“Unity如何實(shí)現(xiàn)圓形Image組件”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Unity如何實(shí)現(xiàn)圓形Image組件”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來(lái)學(xué)習(xí)新知識(shí)吧。

一、前言

游戲里很多圖片都是以圓形展示的,例如頭像、技能圖標(biāo)等,一般做法是使用Image組件+Mask組件實(shí)現(xiàn),但是Mask組件會(huì)影響效率(增加額外的drawcall)所以不建議大量使用

UGUI的Mask實(shí)現(xiàn)原理:利用GPU的模版緩沖

Mask組件會(huì)賦給父級(jí)和子級(jí)UI一個(gè)特殊的材質(zhì),這個(gè)材質(zhì)會(huì)給Image的每個(gè)像素點(diǎn)進(jìn)行標(biāo)記并放在一個(gè)稱為Stencil Buffer的緩存內(nèi),父級(jí)每個(gè)像素點(diǎn)的標(biāo)記設(shè)置為1,子級(jí)UI進(jìn)行渲染的時(shí)候會(huì)去檢查這個(gè)Stencil Buffer內(nèi)的標(biāo)記是否為1,如果為1則進(jìn)行渲染,否則不渲染

Unity如何實(shí)現(xiàn)圓形Image組件

Unity如何實(shí)現(xiàn)圓形Image組件

Unity如何實(shí)現(xiàn)圓形Image組件

二、實(shí)現(xiàn)自己的圓形組件

像Image,RawImage這些組件都是繼承自自MsakGraphics類,MsakGraphics類繼承自Graphic類,Graphic類中有個(gè)OnPopulateMesh方法用于繪制圖形,UGUI的Image組件實(shí)現(xiàn)原理是重寫(xiě)了OnPopulateMesh方法并繪制了一個(gè)矩形,所以按照這個(gè)思路我們可以重寫(xiě)OnPopulateMesh方法直接繪制一個(gè)圓形
——獲取圖片的長(zhǎng)寬、uv等信息

Unity如何實(shí)現(xiàn)圓形Image組件

——OnPopulateMesh:當(dāng)UI元素生成頂點(diǎn)數(shù)據(jù)時(shí)會(huì)調(diào)用OnPopulateMesh(VertexHelper vh)函數(shù),我們只需要將原先的矩形頂點(diǎn)數(shù)據(jù)清除,改寫(xiě)入圓形頂點(diǎn)數(shù)據(jù),這樣渲染出來(lái)的自然是圓形圖片

——不規(guī)則UI元素的響應(yīng)區(qū)域判定
UI組件的響應(yīng)區(qū)域判定是通過(guò)實(shí)現(xiàn)ICanvasRaycastFilter接口中的IsRaycastLocationValid函數(shù),它的返回值是一個(gè)bool值,返回true則視為可以響應(yīng),例如Image組件,它判定了兩個(gè)條件:當(dāng)前屏幕坐標(biāo)是否在當(dāng)前圖片矩形區(qū)域內(nèi)和當(dāng)前屏幕坐標(biāo)的圖片區(qū)域透明度是否大于alphaHitTestMinimumThreshold參數(shù)
我們想實(shí)現(xiàn)精確的點(diǎn)擊判斷,可以代碼動(dòng)態(tài)將alphaHitTestMinimumThreshold參數(shù)設(shè)置為0.1,這樣就實(shí)現(xiàn)了只有在透明度大于0.1的像素點(diǎn)才視為響應(yīng),但它要求圖片的Read/Write Enabled必須開(kāi)啟,這就導(dǎo)致了圖片占用了兩份內(nèi)存,所以不建議使用
對(duì)于像素級(jí)的點(diǎn)擊判定,有一種算法可以實(shí)現(xiàn):Ray-Crossing算法
此算法適用于所有圖形,實(shí)現(xiàn)思路是從指定點(diǎn)向任意方向發(fā)出一條水平射線,與圖形相交,如果交點(diǎn)是奇數(shù)個(gè),則點(diǎn)在圖形內(nèi),如果交點(diǎn)是偶數(shù)個(gè),則點(diǎn)在圖形外

Unity如何實(shí)現(xiàn)圓形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>
    /// 繪制起始點(diǎn)(填充類型-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;
 
    //繪制起始點(diǎn)(填充類型-360度)
    [SerializeField]
    Origin360 m_Origin360;
 
    //是否為順時(shí)針繪制
    [SerializeField]
    bool m_Clockwise;
 
    //填充度
    [SerializeField]
    [Range(0, 1)]
    float m_FillAmount;
 
    //多少個(gè)三角面組成
    [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++;
            }
        }
    }
}

讀到這里,這篇“Unity如何實(shí)現(xiàn)圓形Image組件”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過(guò)才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI