代码如下:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Sprites;
using System.Collections.Generic;
/// <summary>
/// 圆形遮罩
/// @Author: Danny Yan
/// </summary>
[RequireComponent(typeof(Image))]
public class ImageMaskCircle : UnityEngine.UI.BaseMeshEffect
{
[SerializeField]
private float offX = 0;
[SerializeField]
private float offY = 0;
[SerializeField]
[Tooltip("mask方式,true:视觉上mash相对位置不变,图片在mask内滑动 false:视觉上图片不动,mask在图片上滑动")]
private bool _fixMaskViewPort = false;
public bool fixMaskViewPort
{
get { return this._fixMaskViewPort; }
set
{
this._fixMaskViewPort = value;
this.graphic.SetVerticesDirty();
}
}
public bool isInner = false;
private Vector3[] vertices;
private Vector3[] triangles;
private Vector2 uvs;
private Image _image;
public float tickness = 1;
public bool isFill = true;
/** 半径 */
[SerializeField]
[Tooltip("半径")]
private float _radius = 1;
public float radius
{
get { return _radius; }
set
{
if (value < 0) value = 0;
this._radius = value;
}
}
[SerializeField]
[Tooltip("将圆切分为多少段")]
[Range(3, 720)]
private int _segment = 50;
public int segment
{
get { return _segment; }
set
{
if (value < 3) value = 3;
else if (value > 720) value = 720;
this._segment = value;
}
}
[SerializeField]
[Tooltip("开口数-不绘制的段数")]
private int _segmentOpen = 0;
public int segmentOpen
{
get { return _segmentOpen; }
set
{
if (value < 0) value = 0;
else if (value > this._segment) value = this._segment;
this._segmentOpen = value;
}
}
[Tooltip("开口起始角度")]
public float openStartDeg = 0;
[Tooltip("开口方向,是否为顺时针")]
public bool openClockwise = true;
// --------------------------------------------
override protected void Awake()
{
base.Awake();
this._image = this.GetComponent<Image>();
var wh = _image.rectTransform.sizeDelta;
this.radius = Mathf.Min(wh.x, wh.y) * .5f;
}
internal void FixedUpdate()
{
#if UNITY_EDITOR
this.SetCircle(this.offX, this.offY, this.radius);
#endif
}
public void SetCircle(float x, float y, float radius)
{
this.offX = x;
this.offY = y;
this.segmentOpen = _segmentOpen;
var n = this._segment;
this._radius = radius;
if (this.isFill)
{
var list = getCircleVertTri(radius);
this.vertices = list[0];
this.triangles = list[1];
}
else
{
// 外圆
var list = this.getCircleVertTri(radius);
// 内圆
var _radius = radius - this.tickness;
if (_radius < 0) _radius = 0;
var list2 = this.getCircleVertTri(_radius);
// 连接2个圆的顶点
if (this.vertices == null || this.vertices.Length != n * 2)
{
this.vertices = new Vector3[n * 2];
}
if (this.triangles == null || this.triangles.Length != (n - this.segmentOpen) * 2)
{
this.triangles = new Vector3[(n - this.segmentOpen) * 2];
}
for (int i = 1, j = 0; i < list[0].Length; i++, j += 2)
{
this.vertices[j] = list[0][i]; // 外圆的第i个顶点
this.vertices[j + 1] = list2[0][i]; // 内圆的第i个顶点
}
// 以六边形为例,顶点索引为:
// (0,2,1) (2,3,1) (2,4,3) (4,5,3)
// (4,6,5) (6,7,5) (6,8,7) (8,9,7)
// (8,10,9)(10,11,9)(10,0,11)(0,1,11)
// 通过观察,得到以下规律
int a = 0, b = 0, c = 0;
for (int i = 0; i < triangles.Length; i++)
{
// j是在vertices中的索引
var j = this._segmentOpen * 2 + i;
if (j == vertices.Length - 1)
{
a = 0;
}
else
{
a = j % 2 == 0 ? j : j + 1;
}
// 到头部
b = j + 2 - (j + 2 >= vertices.Length ? vertices.Length : 0);
c = (int)Mathf.Floor(j / 2) * 2 + 1;
this.triangles[i] = new Vector3(a, b, c);
}
}
this.graphic.SetVerticesDirty();
}
private List<Vector3[]> getCircleVertTri(float radius)
{
var list = new List<Vector3[]>();
// 每个内角大小
float perAngle = 180f - (180f * (segment - 2) / segment);
float perRadian = perAngle * Mathf.Deg2Rad;
Vector3[] vertexList = new Vector3[segment + 1];
Vector3[] triangleList = new Vector3[segment - this.segmentOpen];
var _offx = this._fixMaskViewPort ? 0 : this.offX;
var _offy = this._fixMaskViewPort ? 0 : this.offY;
// 第一个顶点为圆心
vertexList[0].x = _offx;
vertexList[0].y = _offy;
vertexList[0].z = 0;
// 起始点角度
float curDegree = this.openStartDeg;
// 从起始角度开始计算出圆上的每个顶点
for (int i = 1; i < vertexList.Length; i++)
{
float ratian = curDegree * Mathf.Deg2Rad;
float _x3 = Mathf.Cos(ratian) * radius;
float _y3 = Mathf.Sin(ratian) * radius;
vertexList[i].x = _offx + _x3;
vertexList[i].y = _offy + _y3;
vertexList[i].z = 0;
curDegree -= perAngle * (this.openClockwise ? 1 : -1);
}
var ind = triangleList.Length - 1;
for (int i = 0; i < triangleList.Length; i++)
{
if (i == ind)
{
triangleList[i].x = 0;
triangleList[i].y = vertexList.Length - 1;
triangleList[i].z = 1;
}
else
{
// 剔除掉需要排除的段
triangleList[i].x = 0;
triangleList[i].y = this.segmentOpen + i + 1;
triangleList[i].z = this.segmentOpen + i + 2;
}
}
list.Add(vertexList);
list.Add(triangleList);
return list;
}
override public void ModifyMesh(VertexHelper vh)
{
if (!this.enabled || this._image == null) return;
vh.Clear();
float tw = _image.rectTransform.rect.width;
float th = _image.rectTransform.rect.height;
// uv映射得到宽高. GetOuterUV获取到包含所有像素的UV, GetInnerUV在遇到渐变图片时返回的uv可能大于1,最终会产生放大效果
Vector4 uv = _image.overrideSprite != null ? (isInner ? DataUtility.GetInnerUV(_image.overrideSprite) : DataUtility.GetOuterUV(_image.overrideSprite)) : Vector4.zero;
float w = uv.z - uv.x;
float h = uv.w - uv.y;
float uvScaleX = w / tw; // 宽的比例
float uvScaleY = h / th; // 高的比例
float uvCenterX = uv.x + w * _image.rectTransform.pivot.x;
float uvCenterY = uv.y + h * _image.rectTransform.pivot.y;
UIVertex uivert = UIVertex.simpleVert;
for (int i = 0; i < (this.vertices == null ? 0 : this.vertices.Length); i++)
{
uivert.color = _image.color;
uivert.position = this.vertices[i];
if (this._fixMaskViewPort)
{
this.uvs.x = (this.vertices[i].x + offX) * uvScaleX + uvCenterX;
this.uvs.y = (this.vertices[i].y + offY) * uvScaleY + uvCenterY;
}
else
{
this.uvs.x = this.vertices[i].x * uvScaleX + uvCenterX;
this.uvs.y = this.vertices[i].y * uvScaleY + uvCenterY;
}
uivert.uv0 = this.uvs;
vh.AddVert(uivert);
}
for (int i = 0; i < (this.triangles == null ? 0 : this.triangles.Length); i++)
{
var tri = this.triangles[i];
vh.AddTriangle((int)tri.x, (int)tri.y, (int)tri.z);
}
}
}
原始图像如下:
使用以下值:
得到: