您好,登錄后才能下訂單哦!
本文小編為大家詳細(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)行渲染,否則不渲染
像Image,RawImage這些組件都是繼承自自MsakGraphics類,MsakGraphics類繼承自Graphic類,Graphic類中有個(gè)OnPopulateMesh方法用于繪制圖形,UGUI的Image組件實(shí)現(xiàn)原理是重寫(xiě)了OnPopulateMesh方法并繪制了一個(gè)矩形,所以按照這個(gè)思路我們可以重寫(xiě)OnPopulateMesh方法直接繪制一個(gè)圓形
——獲取圖片的長(zhǎng)寬、uv等信息
——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)在圖形外
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è)資訊頻道。
免責(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)容。