Unity中实现新手引导的镂空效果

一:实现思路

创建有8个顶点的Mesh,内外边界都是四边形(矩形)。只生成内、外边之间的Mesh,内层矩形就产生了镂空部分,外层的4个顶点,是组件自身RectTransform的四个顶点,内层的4个顶点,使用镂空目标(_target)RectTransform的四个顶点。确定内层的顶点的时候需要注意,多数情况下_target和HollowOutMask都不在同一个本地坐标空间,所以需要使用CalculateRelativeRectTransformBounds计算出HollowOutMask空间下坐标
这种镂空的表现,可以稍稍提高下性能。因为镂空的位置不参与渲染,Overdraw会降低

UGUI提供了ICanvasRaycastFilter接口,我们实现IsRaycastLocationValid方法,就可以很方便的控制HollowOutMask是否要拦截下在某一点触发的事件


二:这个组件的作用

这个组件做了两件事情:表现上镂空一块区域和不拦截镂空范围上的事件


三:代码实现

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// 实现镂空效果的Mask组件
/// </summary>
public class HollowOutMask : MaskableGraphic, ICanvasRaycastFilter
{
    [SerializeField]
    private RectTransform _target;

    private Vector3 _targetMin = Vector3.zero;
    private Vector3 _targetMax = Vector3.zero;

    private bool _canRefresh = true;
    private Transform _cacheTrans = null;

    /// <summary>
    /// 设置镂空的目标
    /// </summary>
    public void SetTarget(RectTransform target)
    {
        _canRefresh = true;
        _target = target;
        _RefreshView();
    }

    private void _SetTarget(Vector3 tarMin, Vector3 tarMax)
    {
        if (tarMin == _targetMin && tarMax == _targetMax)
            return;
        _targetMin = tarMin;
        _targetMax = tarMax;
        SetAllDirty();
    }

    private void _RefreshView()
    {
        if (!_canRefresh) return;
        _canRefresh = false;

        if (null == _target)
        {
            _SetTarget(Vector3.zero, Vector3.zero);
            SetAllDirty();
        }
        else
        {
            Bounds bounds = RectTransformUtility.CalculateRelativeRectTransformBounds(_cacheTrans, _target);
            _SetTarget(bounds.min, bounds.max);
        }
    }

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        if (_targetMin == Vector3.zero && _targetMax == Vector3.zero)
        {
            base.OnPopulateMesh(vh);
            return;
        }

        vh.Clear();

        // 填充顶点
        UIVertex vert = UIVertex.simpleVert;
        vert.color = color;

        Vector2 selfPiovt = rectTransform.pivot;
        Rect selfRect = rectTransform.rect;
        float outerLx = -selfPiovt.x * selfRect.width;
        float outerBy = -selfPiovt.y * selfRect.height;
        float outerRx = (1 - selfPiovt.x) * selfRect.width;
        float outerTy = (1 - selfPiovt.y) * selfRect.height;
        // 0 - Outer:LT
        vert.position = new Vector3(outerLx, outerTy);
        vh.AddVert(vert);
        // 1 - Outer:RT
        vert.position = new Vector3(outerRx, outerTy);
        vh.AddVert(vert);
        // 2 - Outer:RB
        vert.position = new Vector3(outerRx, outerBy);
        vh.AddVert(vert);
        // 3 - Outer:LB
        vert.position = new Vector3(outerLx, outerBy);
        vh.AddVert(vert);

        // 4 - Inner:LT
        vert.position = new Vector3(_targetMin.x, _targetMax.y);
        vh.AddVert(vert);
        // 5 - Inner:RT
        vert.position = new Vector3(_targetMax.x, _targetMax.y);
        vh.AddVert(vert);
        // 6 - Inner:RB
        vert.position = new Vector3(_targetMax.x, _targetMin.y);
        vh.AddVert(vert);
        // 7 - Inner:LB
        vert.position = new Vector3(_targetMin.x, _targetMin.y);
        vh.AddVert(vert);

        // 设定三角形
        vh.AddTriangle(4, 0, 1);
        vh.AddTriangle(4, 1, 5);
        vh.AddTriangle(5, 1, 2);
        vh.AddTriangle(5, 2, 6);
        vh.AddTriangle(6, 2, 3);
        vh.AddTriangle(6, 3, 7);
        vh.AddTriangle(7, 3, 0);
        vh.AddTriangle(7, 0, 4);
    }

    bool ICanvasRaycastFilter.IsRaycastLocationValid(Vector2 screenPos, Camera eventCamera)
    {
        if (null == _target) return true;
        // 将目标对象范围内的事件镂空(使其穿过)
        return !RectTransformUtility.RectangleContainsScreenPoint(_target, screenPos, eventCamera);
    }

    protected override void Awake()
    {
        base.Awake();
        _cacheTrans = GetComponent<RectTransform>();
    }

#if UNITY_EDITOR
    void Update()
    {
        _canRefresh = true;
        _RefreshView();
    }
#endif
}

四:使用说明

将以上组件挂载到有RectTransform组件的游戏物体身上,设置Color的颜色以及Target区域的大小即可
——此组件挂载的游戏物体身上只能有一个继承Graphics类的组件
——若自定义添加Image控制Target区域大小,记得将Image的Alpha设置为0并且取消射线检测

发布了138 篇原创文章 · 获赞 292 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/LLLLL__/article/details/104263122