昨天在改项目的时候,看到人物头像是圆形的,我就想到用UGUI的Mask组件,但是我试了一下,效果不是很好,而且性能上也不是很理想,所以我就找到一个比较好的解决办法,之前也考虑过用Image上的Material去弄,但是也不是很完美,后来看到一篇文章,说是重写Image组件,所以就有了下面我要提到的东西(只支持53版本以上)。文章源自大腿Plus-https://www.shijunzh.com/archives/458
首先需要重写Image,由于继承UnityEngine基类的派生类不能在Inspector里显示自定义参数。所以我们要新建BaseImage类来代替Image类。原Image源码有近千行代码,BaseImage对其进行了部分精简,只支持Simple Image Type,并去掉了eventAlphaThreshold的相关代码。经过删减,得到一个百行代码的BaseImage类,精简版Image就完成了。文章源自大腿Plus-https://www.shijunzh.com/archives/458
using System; using UnityEngine; using System.Collections; using UnityEngine.Serialization; using UnityEngine.UI; public class BaseImage : MaskableGraphic,ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } [FormerlySerializedAs("m_Frame")] [SerializeField] private Sprite m_Sprite; public Sprite sprite { get { return m_Sprite; } set { if (SetPropertyUtilityExt.SetClass(ref m_Sprite, value)) SetAllDirty(); } } [NonSerialized] private Sprite m_OverrideSprite; public Sprite overrideSprite { get { return m_OverrideSprite == null ? sprite : m_OverrideSprite; } set { if (SetPropertyUtilityExt.SetClass(ref m_OverrideSprite, value)) SetAllDirty(); } } /// <summary> /// Image's texture comes from the UnityEngine.Image. /// </summary> public override Texture mainTexture { get { return overrideSprite == null ? s_WhiteTexture : overrideSprite.texture; } } public float pixelsPerUnit { get { float spritePixelsPerUnit = 100; if (sprite) spritePixelsPerUnit = sprite.pixelsPerUnit; float referencePixelsPerUnit = 100; if (canvas) referencePixelsPerUnit = canvas.referencePixelsPerUnit; return spritePixelsPerUnit / referencePixelsPerUnit; } } /// <summary> /// 子类需要重写该方法来自定义Image形状 /// </summary> /// <param name="vh"></param> protected override void OnPopulateMesh(VertexHelper vh) { base.OnPopulateMesh(vh); } #region ISerializationCallbackReceiver public void OnAfterDeserialize() { } // // 摘要: // Implement this method to receive a callback after unity serialized your object. public void OnBeforeSerialize() { } #endregion #region ILayoutElement public virtual void CalculateLayoutInputHorizontal() { } public virtual void CalculateLayoutInputVertical() { } public virtual float minWidth { get { return 0; } } public virtual float preferredWidth { get { if (overrideSprite == null) return 0; return overrideSprite.rect.size.x / pixelsPerUnit; } } public virtual float flexibleWidth { get { return -1; } } public virtual float minHeight { get { return 0; } } public virtual float preferredHeight { get { if (overrideSprite == null) return 0; return overrideSprite.rect.size.y / pixelsPerUnit; } } public virtual float flexibleHeight { get { return -1; } } public virtual int layoutPriority { get { return 0; } } #endregion #region ICanvasRaycastFilter public virtual bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) { return true; } #endregion }
然后,开始写CricleImage类,Image类继承自MaskableGraphic,实现了ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter这三个接口。最关键的是MaskableGraphic类,MaskableGraphic负责绘制逻辑,MaskableGraphic继承自Graphic,Graphic里有个OnPopulateMesh函数,这正是我们需要的函数。文章源自大腿Plus-https://www.shijunzh.com/archives/458
UI元素生成顶点数据时会调用OnPopulateMesh(VertexHelper vh)函数,我们只要继承改写OnPopulateMesh函数,将原先的矩形顶点数据清除,改写入圆形顶点数据,这样渲染出来的自然是圆形图片。文章源自大腿Plus-https://www.shijunzh.com/archives/458
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.Sprites; [AddComponentMenu("UI/Circle Image")] public class CircleImage : BaseImage { // Use this for initialization void Awake() { innerVertices = new List<Vector3>(); outterVertices = new List<Vector3>(); } // Update is called once per frame void Update() { this.thickness = (float)Mathf.Clamp(this.thickness, 0, rectTransform.rect.width / 2); } [Tooltip("圆形或扇形填充比例")] [Range(0, 1)] public float fillPercent = 1f; [Tooltip("是否填充圆形")] public bool fill = true; [Tooltip("圆环宽度")] public float thickness = 5; [Tooltip("圆形")] [Range(3, 100)] public int segements = 20; private List<Vector3> innerVertices; private List<Vector3> outterVertices; protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); innerVertices.Clear(); outterVertices.Clear(); float degreeDelta = (float)(2 * Mathf.PI / segements); int curSegements = (int)(segements * fillPercent); float tw = rectTransform.rect.width; float th = rectTransform.rect.height; float outerRadius = rectTransform.pivot.x * tw; float innerRadius = rectTransform.pivot.x * tw - thickness; Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero; float uvCenterX = (uv.x + uv.z) * 0.5f; float uvCenterY = (uv.y + uv.w) * 0.5f; float uvScaleX = (uv.z - uv.x) / tw; float uvScaleY = (uv.w - uv.y) / th; float curDegree = 0; UIVertex uiVertex; int verticeCount; int triangleCount; Vector2 curVertice; if (fill) //圆形 { curVertice = Vector2.zero; verticeCount = curSegements + 1; uiVertex = new UIVertex(); uiVertex.color = color; uiVertex.position = curVertice; uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY); vh.AddVert(uiVertex); for (int i = 1; i < verticeCount; i++) { float cosA = Mathf.Cos(curDegree); float sinA = Mathf.Sin(curDegree); curVertice = new Vector2(cosA * outerRadius, sinA * outerRadius); curDegree += degreeDelta; uiVertex = new UIVertex(); uiVertex.color = color; uiVertex.position = curVertice; uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY); vh.AddVert(uiVertex); outterVertices.Add(curVertice); } triangleCount = curSegements * 3; for (int i = 0, vIdx = 1; i < triangleCount - 3; i += 3, vIdx++) { vh.AddTriangle(vIdx, 0, vIdx + 1); } if (fillPercent == 1) { //首尾顶点相连 vh.AddTriangle(verticeCount - 1, 0, 1); } } else//圆环 { verticeCount = curSegements * 2; for (int i = 0; i < verticeCount; i += 2) { float cosA = Mathf.Cos(curDegree); float sinA = Mathf.Sin(curDegree); curDegree += degreeDelta; curVertice = new Vector3(cosA * innerRadius, sinA * innerRadius); uiVertex = new UIVertex(); uiVertex.color = color; uiVertex.position = curVertice; uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY); vh.AddVert(uiVertex); innerVertices.Add(curVertice); curVertice = new Vector3(cosA * outerRadius, sinA * outerRadius); uiVertex = new UIVertex(); uiVertex.color = color; uiVertex.position = curVertice; uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY); vh.AddVert(uiVertex); outterVertices.Add(curVertice); } triangleCount = curSegements * 3 * 2; for (int i = 0, vIdx = 0; i < triangleCount - 6; i += 6, vIdx += 2) { vh.AddTriangle(vIdx + 1, vIdx, vIdx + 3); vh.AddTriangle(vIdx, vIdx + 2, vIdx + 3); } if (fillPercent == 1) { //首尾顶点相连 vh.AddTriangle(verticeCount - 1, verticeCount - 2, 1); vh.AddTriangle(verticeCount - 2, 0, 1); } } } public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) { Sprite sprite = overrideSprite; if (sprite == null) return true; Vector2 local; RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local); return Contains(local, outterVertices, innerVertices); } private bool Contains(Vector2 p, List<Vector3> outterVertices, List<Vector3> innerVertices) { var crossNumber = 0; RayCrossing(p, innerVertices, ref crossNumber);//检测内环 RayCrossing(p, outterVertices, ref crossNumber);//检测外环 return (crossNumber & 1) == 1; } /// <summary> /// 使用RayCrossing算法判断点击点是否在封闭多边形里 /// </summary> /// <param name="p"></param> /// <param name="vertices"></param> /// <param name="crossNumber"></param> private void RayCrossing(Vector2 p, List<Vector3> vertices, ref int crossNumber) { for (int i = 0, count = vertices.Count; i < count; i++) { var v1 = vertices[i]; var v2 = vertices[(i + 1) % count]; //点击点水平线必须与两顶点线段相交 if (((v1.y <= p.y) && (v2.y > p.y)) || ((v1.y > p.y) && (v2.y <= p.y))) { //只考虑点击点右侧方向,点击点水平线与线段相交,且交点x > 点击点x,则crossNumber+1 if (p.x < v1.x + (p.y - v1.y) / (v2.y - v1.y) * (v2.x - v1.x)) { crossNumber += 1; } } } } }
最后,还有一个辅助类文章源自大腿Plus-https://www.shijunzh.com/archives/458
using UnityEngine; internal static class SetPropertyUtilityExt { public static bool SetColor(ref Color currentValue, Color newValue) { if (currentValue.r == newValue.r && currentValue.g == newValue.g && currentValue.b == newValue.b && currentValue.a == newValue.a) return false; currentValue = newValue; return true; } public static bool SetStruct<T>(ref T currentValue, T newValue) where T : struct { if (currentValue.Equals(newValue)) return false; currentValue = newValue; return true; } public static bool SetClass<T>(ref T currentValue, T newValue) where T : class { if ((currentValue == null && newValue == null) || (currentValue != null && currentValue.Equals(newValue))) return false; currentValue = newValue; return true; } }

上面就是所有代码了,上面还加入了圆形区域的点击检测,是通过Ray-Crossing算法判断的,也就是由这个点发射一条射线与多边形相交,如果交点是奇数,说明在多边形内,如果是偶数个就是在多边形外面。下面是package包,有需要的去下载吧。文章源自大腿Plus-https://www.shijunzh.com/archives/458
下载链接: https://pan.baidu.com/s/1ik-LJtdJDvC8SpMtGncGqQ 提取码: hxku文章源自大腿Plus-https://www.shijunzh.com/archives/458
顺便提一下我找到新工作了,感觉还不错,最近还没找到房子,所以现在更新的少了,等找到房子后会及时更新的。文章源自大腿Plus-https://www.shijunzh.com/archives/458
2017年3月25日 下午4:15 1F
登录回复
赵哥,找的新工作在什么地方?
2017年3月27日 上午10:37 B1
登录回复
@ 0过河小卒0 朝阳四惠这边
2017年3月27日 上午11:24 B2
登录回复
@ 大腿Plus 我就在四惠这上班,在 住邦 ,远洋旁边
2017年3月27日 下午12:20 B3
登录回复
@ 0过河小卒0 我也在住邦 我在1号楼
2017年3月27日 下午1:43 2F
登录回复
我在3号楼,好近啊。
2017年3月27日 下午2:03 B1
登录回复
@ 0过河小卒0 对啊