本文小編為大家詳細介紹“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++; } } } }
讀到這里,這篇“Unity如何實現圓形Image組件”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。