优化一下之前的代码,封装成几个API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| using UnityEngine; using UnityEngine.Sprites; using UnityEngine.UI;
public class CircleImage : Image { [SerializeField] private int segments = 100; [SerializeField] private float showPercent = 1; private readonly Color32 GRAY_COLOR = new Color32(60, 60, 60, 255); protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); AddVertex(vh, segments); AddTriangle(vh, segments); } private void AddVertex(VertexHelper vh,int segments) { float width = rectTransform.rect.width; float height = rectTransform.rect.height; int realSegment = (int)(segments * showPercent);
Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero; float uvWidth = uv.z - uv.x; float uvHeight = uv.w - uv.y; Vector2 uvCenter = new Vector2(uvWidth * 0.5f, uvHeight * 0.5f); Vector2 convertRatio = new Vector2(uvWidth / width, uvHeight / height);
float radian = (2 * Mathf.PI) / segments; float radius = width * 0.5f;
Vector2 originPos = new Vector2((0.5f - rectTransform.pivot.x) * width, (0.5f - rectTransform.pivot.y) * height); Vector2 vertPos = Vector2.zero;
Color32 colorTemp = GetOriginColor(); UIVertex origin = GetUIVertex(colorTemp, originPos, vertPos, uvCenter, convertRatio); vh.AddVert(origin);
int vertexCount = realSegment + 1; float curRadian = 0; Vector2 posTemp; for (int i = 0; i < segments + 1; i++) { float x = Mathf.Cos(curRadian) * radius; float y = Mathf.Sin(curRadian) * radius; curRadian += radian;
if (i < vertexCount - 1) { colorTemp = color; } else if (showPercent >= 1) { colorTemp = color; } else { colorTemp = GRAY_COLOR; } posTemp = new Vector2(x, y); UIVertex vertexTemp = GetUIVertex(colorTemp, posTemp + originPos, posTemp, uvCenter, convertRatio); vh.AddVert(vertexTemp); } } private Color32 GetOriginColor() { Color32 colorTemp = (Color.white - GRAY_COLOR) * showPercent; return new Color32( (byte)(GRAY_COLOR.r + colorTemp.r), (byte)(GRAY_COLOR.g + colorTemp.g), (byte)(GRAY_COLOR.b + colorTemp.b), 255); } private void AddTriangle(VertexHelper vh, int realSegments) { int id = 1; for (int i = 0; i < realSegments; i++) { vh.AddTriangle(id, 0, id + 1); id++; } } private UIVertex GetUIVertex(Color32 color, Vector3 pos, Vector2 uvPos,Vector2 uvCenter,Vector2 uvScale) { UIVertex vertexTemp = new UIVertex(); vertexTemp.color = color; vertexTemp.position = pos; vertexTemp.uv0 = new Vector2(uvPos.x * uvScale.x + uvCenter.x, uvPos.y * uvScale.y + uvCenter.y); return vertexTemp; } }
|
注意GetOriginColor
方法,这里是将补色给加上,上一节的代码其实是个半透明的颜色。
添加点击相应逻辑
目前我们只是实现了“画圆”,还没有实现点击逻辑。
控制响应区域
首先,我们需要实现一个算法,这个算法规定只有点击了“圆形”内部才能响应点击事件。即使是五边形,六边形,也是只有内部才能响应。这个算法的逻辑是我们从点击的位置发射一条(向右的)射线,如果和图形的边缘的交点是奇数个,说明点击的位置是在图形内部,如果是偶数个,就是在外部。
修改CircleImage
,添加一个List,记录所有的边的顶点(圆心顶点除外)位置。注意,这里记录的是RectTransform的位置。
1 2 3 4 5 6 7 8 9 10 11 12
| private List<Vector3> mVertexList;
private void AddVertex(VertexHelper vh,int segments) { for (int i = 0; i < segments + 1; i++) { vh.AddVert(vertexTemp); mVertexList.Add(posTemp + originPos); } }
|
然后添加如下方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera) { Vector2 localPoint; RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera,out localPoint); return IsValid(localPoint); } private bool IsValid(Vector2 localPoint) { return GetCrossPointNum(localPoint,mVertexList) % 2 == 1; } private int GetCrossPointNum(Vector2 localPoint,List<Vector3> vertexList) { int crossCount = 0; Vector3 vert1 = Vector3.zero; Vector3 vert2 = Vector3.zero; int count = vertexList.Count; for (int i = 0; i < count; i++) { vert1 = vertexList[i]; vert2 = vertexList[(i + 1) % count]; if (IsYInRange(localPoint, vert1, vert2)) { if (localPoint.x < GetX(vert1, vert2, localPoint.y)) { crossCount++; } } } return crossCount; } private bool IsYInRange(Vector2 localPoint, Vector3 vert1, Vector3 vert2) { if(vert1.y > vert2.y) { return localPoint.y < vert1.y && localPoint.y > vert2.y; } else { return localPoint.y > vert1.y && localPoint.y < vert2.y; } } private float GetX(Vector3 vert1, Vector3 vert2,float y) { float k = (vert1.y - vert2.y) / (vert1.x - vert2.x); return vert1.x + (y - vert1.y) / k; }
|
IsRaycastLocationValid
是Image内部的虚方法,只有它返回true时才表示这个图片被Canvas的Graphic射线投射到了。
IsValid
是判断点击位置向右与图形边缘的交点是不是奇数,是奇数就表示在图形内部了
GetCrossPointNum
是计算交点数量的方法。
IsYInRange
是判断点击位置的y值是否在当前计算的两个顶点y值之间的方法,如果点击位置没有在两个顶点之间,那么就没有计算交点的必要了。
GetX
是点击位置向右的射线和图形边缘相交时,返回交点x坐标的方法,用到了直线公式y = kx + b。只要交点x坐标比点击位置x坐标大,就说明相交了。
此时我们点击图形,会发现图形边缘空白部分点击后,图形不会响应了(配合Button组件)。