Unity Mask镂空效果(常用于新手引导或高亮显示UI元素)

在游戏中经常可以看到新手引导的效果是全屏上方有一张黑色遮罩,指定区块高亮展示,并且鼠标的点击事件可以穿透黑色遮罩点击到下方的按钮。(如下图所示)

那这个效果是怎么实现的呢?下面提供两种实现方案:

1、使用四个canvas进行拼接(理解成本低,但不推荐)

这样能保证界面中可以保留一个矩形的空白区块。由于中心区块没有任何阻挡射线检测的障碍,因此也能满足点击区域内按钮的需求

只需要知道空白区域的Rect数据,就可以计算出4个Canvas的尺寸以及位置。

优点:理解成本低,实现方法简单

缺点:Canvas之间的拼接可能会出现缝隙(与UI界面渲染逻辑有关,不太好避免)

2、重写MaskableGraphic,实现挖空效果(推荐)

主要分两步实现:

①美术效果层面的镂空展示

②射线检测层面的过滤

美术效果层面的镂空展示

OnPopulateMesh 算是一个比较经典的函数,这个可以让我们实现图片的裁剪

例如我们需要裁切中间区域:

1、先绘制一个覆盖整个屏幕的矩形(外部矩形)

2、在目标区域(内部矩形)挖空,形成镂空效果

3、顶点和三角形设置:创建8个顶点(4个外部顶点,4个内部顶点),然后通过8个三角形连接这些顶点,形成外部矩形和内部镂空区域之间的边框。

通过这种方法,实现"空白区域"的镂空效果

射线检测层面的过滤

实现`ICanvasRaycastFilter`接口的`IsRaycastLocationValid`方法

在目标区域内的点击事件可以穿透(返回false)

遮罩部分会拦截事件(返回true)

这样,只有遮罩部分响应事件,目标区域则允许事件穿透到后面的UI元素。

代码实现

cs 复制代码
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

namespace GameComponent
{
    public class GuideMask : 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
            {
                StartCoroutine(RefreshNextFrame());
            }
        }

        private IEnumerator RefreshNextFrame()
        {
            yield return null;
            Vector3 targetLocalPos = _cacheTrans.InverseTransformPoint(_target.position);
                
            Rect targetRect = _target.rect;
            Vector2 pivot = _target.pivot;
                
            float leftOffset = targetRect.width * pivot.x;
            float rightOffset = targetRect.width * (1 - pivot.x);
            float bottomOffset = targetRect.height * pivot.y;
            float topOffset = targetRect.height * (1 - pivot.y);
                
            Vector3 targetMin = new Vector3(
                targetLocalPos.x - leftOffset, 
                targetLocalPos.y - bottomOffset, 
                0
            );
            Vector3 targetMax = new Vector3(
                targetLocalPos.x + rightOffset, 
                targetLocalPos.y + topOffset, 
                0
            );
                
            _SetTarget(targetMin, targetMax);
        }
        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
    }
}

使用方法

使用GuideMask.SetTarget()赋值需要镂空的RectTransform即可

相关推荐
偶尔的鼠标人13 小时前
Avalonia中,使用DataTable类型作为DataGrid的ItemSource 数据源
ui·c#·avalonia
左手吻左脸。14 小时前
Element UI表格中根据数值动态设置字体颜色
vue.js·ui·elementui
土丁爱吃大米饭15 小时前
千年游戏智慧:文化的密码
游戏·游戏设计·古代游戏·游戏中的文化·游戏文化
我的xiaodoujiao17 小时前
从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 8--基础知识 4--常用函数 2
前端·python·测试工具·ui
我命由我1234519 小时前
Photoshop - Photoshop 工具栏(10)透视裁剪工具
经验分享·笔记·学习·ui·职场和发展·职场发展·photoshop
ziyue757519 小时前
vue修改element-ui的默认的class
前端·vue.js·ui
我都学杂了。。。21 小时前
Python的循环技巧与性能优化实战
ui
zhangzhangkeji1 天前
cesium126,230217,Pixel Streaming in Unreal Engine 像素流 - 1 基本概念:
游戏引擎·虚幻
2501_918126911 天前
使用HTML和Python开发街霸游戏
python·游戏·html