Unity實(shí)現(xiàn)圓形Image組件
本文實(shí)例為大家分享了Unity實(shí)現(xiàn)圓形Image組件的具體代碼,供大家參考,具體內(nèi)容如下
一、前言
游戲里很多圖片都是以圓形展示的,例如頭像、技能圖標(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)行渲染,否則不渲染
二、實(shí)現(xiàn)自己的圓形組件
像Image,RawImage這些組件都是繼承自自MsakGraphics類,MsakGraphics類繼承自Graphic類,Graphic類中有個(gè)OnPopulateMesh方法用于繪制圖形,UGUI的Image組件實(shí)現(xiàn)原理是重寫了OnPopulateMesh方法并繪制了一個(gè)矩形,所以按照這個(gè)思路我們可以重寫OnPopulateMesh方法直接繪制一個(gè)圓形
——獲取圖片的長寬、uv等信息
——OnPopulateMesh:當(dāng)UI元素生成頂點(diǎn)數(shù)據(jù)時(shí)會(huì)調(diào)用OnPopulateMesh(VertexHelper vh)函數(shù),我們只需要將原先的矩形頂點(diǎn)數(shù)據(jù)清除,改寫入圓形頂點(diǎn)數(shù)據(jù),這樣渲染出來的自然是圓形圖片
——不規(guī)則UI元素的響應(yīng)區(qū)域判定
UI組件的響應(yīng)區(qū)域判定是通過實(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必須開啟,這就導(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++; ? ? ? ? ? ? } ? ? ? ? } ? ? } }
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
使用xmltextreader對(duì)象讀取xml文檔示例
這篇文章主要介紹了使用xmltextreader對(duì)象讀取xml文檔的示例,需要的朋友可以參考下2014-02-02基于Silverlight打印的使用詳解,是否為微軟的Bug問題
本篇文章對(duì)Silverlight打印的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05C#百萬數(shù)據(jù)查詢出現(xiàn)超時(shí)問題的解決方法
這篇文章主要介紹了C#百萬數(shù)據(jù)查詢出現(xiàn)超時(shí)問題的解決方法,是非常實(shí)用的技巧,需要的朋友可以參考下2014-09-09C# Split函數(shù)根據(jù)特定分隔符分割字符串的操作
這篇文章主要介紹了C# Split函數(shù)根據(jù)特定分隔符分割字符串的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12C#操作配置文件app.config、web.config增刪改
這篇文章介紹了C#操作配置文件app.config、web.config增刪改的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05C#基于WebBrowser獲取cookie的實(shí)現(xiàn)方法
這篇文章主要介紹了C#基于WebBrowser獲取cookie的實(shí)現(xiàn)方法,實(shí)例分析了C#基于WebBrowser簡單讀取瀏覽谷歌網(wǎng)站cookie的相關(guān)技巧,非常簡單實(shí)用,需要的朋友可以參考下2015-11-11