问题和解决方案
现在要完成一个需求,即镂空中间部分的image,外围image可以定义颜色并可选屏蔽点击,而中间的image需要透明且可以穿透,必须不能屏蔽点击。
由此拆分成了两个问题:
1.定义外围image颜色,内部image不绘制
2.外围image可选raycast target,内围image不受raycast target标志位影响。
由此可继承Graphic和ICanvasRaycastFilter来分别实现这个目标。
OnPopulateMesh
继承graphic脚本后重写Graphic.OnPopulateMesh函数,可以在里面去完成重绘操作。与其说不绘制中间的这部分范围,不如说只绘制外面的范围。用VertexHelper.AddTriangle即可完成简单的三角形绘制工作,由此可以根据外image顶点和内image顶点来完成八个三角形的构建绘制工作,如下示意图所示:
因此代码如下所示:
protected override void OnPopulateMesh(VertexHelper vh)
{
if (innerTrans == null)
{
base.OnPopulateMesh(vh);
return;
}
vh.Clear();
UIVertex vertex = UIVertex.simpleVert;
vertex.color = color;
//0 outer左下角
vertex.position = new Vector3(outerLeftBottom.x, outerLeftBottom.y);
vh.AddVert(vertex);
//1 outer左上角
vertex.position = new Vector3(outerLeftBottom.x, outerRightTop.y);
vh.AddVert(vertex);
//2 outer右上角
vertex.position = new Vector3(outerRightTop.x, outerRightTop.y);
vh.AddVert(vertex);
//3 outer右下角
vertex.position = new Vector3(outerRightTop.x, outerLeftBottom.y);
vh.AddVert(vertex);
//4 inner左下角
vertex.position = new Vector3(innerLeftBottom.x, innerLeftBottom.y);
vh.AddVert(vertex);
//5 inner左上角
vertex.position = new Vector3(innerLeftBottom.x, innerLeftTop.y);
vh.AddVert(vertex);
//6 inner右上角
vertex.position = new Vector3(innerLeftTop.x, innerLeftTop.y);
vh.AddVert(vertex);
//7 inner右下角
vertex.position = new Vector3(innerLeftTop.x, innerLeftBottom.y);
vh.AddVert(vertex);
//绘制三角形
vh.AddTriangle(0, 1, 4);
vh.AddTriangle(1, 4, 5);
vh.AddTriangle(1, 5, 2);
vh.AddTriangle(2, 5, 6);
vh.AddTriangle(2, 6, 3);
vh.AddTriangle(6, 3, 7);
vh.AddTriangle(4, 7, 3);
vh.AddTriangle(0, 4, 3);
}
IsRaycastLocationValid
继承ICanvasRaycastFilter接口,重写内置的IsRaycastLocationValid即可:
return innerTrans == null || !RectTransformUtility.RectangleContainsScreenPoint(innerTrans, sp, eventCamera);
整体代码:
public class HollowOutMask : Graphic, ICanvasRaycastFilter
{
[Header("镂空区域")]
public RectTransform innerTrans;
RectTransform outerTrans;//背景区域
Vector3 innerLeftTop = Vector3.zero;//镂空区域的右上角坐标
Vector3 innerLeftBottom = Vector3.zero;//镂空区域的左下角坐标
Vector2 outerRightTop = Vector2.zero;//背景区域的右上角坐标
Vector2 outerLeftBottom = Vector2.zero;//背景区域的左下角坐标
protected override void Awake()
{
base.Awake();
outerTrans = GetComponent<RectTransform>();
}
protected override void OnPopulateMesh(VertexHelper vh)
{
if (innerTrans == null)
{
base.OnPopulateMesh(vh);
return;
}
vh.Clear();
UIVertex vertex = UIVertex.simpleVert;
vertex.color = color;
//0 outer左下角
vertex.position = new Vector3(outerLeftBottom.x, outerLeftBottom.y);
vh.AddVert(vertex);
//1 outer左上角
vertex.position = new Vector3(outerLeftBottom.x, outerRightTop.y);
vh.AddVert(vertex);
//2 outer右上角
vertex.position = new Vector3(outerRightTop.x, outerRightTop.y);
vh.AddVert(vertex);
//3 outer右下角
vertex.position = new Vector3(outerRightTop.x, outerLeftBottom.y);
vh.AddVert(vertex);
//4 inner左下角
vertex.position = new Vector3(innerLeftBottom.x, innerLeftBottom.y);
vh.AddVert(vertex);
//5 inner左上角
vertex.position = new Vector3(innerLeftBottom.x, innerLeftTop.y);
vh.AddVert(vertex);
//6 inner右上角
vertex.position = new Vector3(innerLeftTop.x, innerLeftTop.y);
vh.AddVert(vertex);
//7 inner右下角
vertex.position = new Vector3(innerLeftTop.x, innerLeftBottom.y);
vh.AddVert(vertex);
//绘制三角形
vh.AddTriangle(0, 1, 4);
vh.AddTriangle(1, 4, 5);
vh.AddTriangle(1, 5, 2);
vh.AddTriangle(2, 5, 6);
vh.AddTriangle(2, 6, 3);
vh.AddTriangle(6, 3, 7);
vh.AddTriangle(4, 7, 3);
vh.AddTriangle(0, 4, 3);
}
/// <summary>
/// 过滤掉射线检测
/// </summary>
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
return innerTrans == null || !RectTransformUtility.RectangleContainsScreenPoint(innerTrans, sp, eventCamera);
}
private bool NeedUpdateBounds()
{
if (innerTrans == null)
{
return false;
}
Bounds bounds = RectTransformUtility.CalculateRelativeRectTransformBounds(outerTrans, innerTrans);
if (innerLeftTop == bounds.max
&& innerLeftBottom == bounds.min
&& outerRightTop == outerTrans.rect.max
&& outerLeftBottom == outerTrans.rect.min)
{
return false;
}
return true;
}
private void UpdateBounds()
{
if (innerTrans == null)
{
return;
}
Bounds bounds = RectTransformUtility.CalculateRelativeRectTransformBounds(outerTrans, innerTrans);
innerLeftTop = bounds.max;
innerLeftBottom = bounds.min;
outerRightTop = outerTrans.rect.max;
outerLeftBottom = outerTrans.rect.min;
}
private void Update()
{
if(NeedUpdateBounds())
{
UpdateBounds();
SetAllDirty();
}
}
}
注意我们需要赋值一个innerImage transform给这个graphic脚本,作为内image大小和位置控制;而外image则调整挂载这个graphic脚本本身的transform即可。