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即可

相关推荐
江上闲人.11 小时前
Wardogs战犬\战狗Beta测试启动报错UE崩溃卡顿?解决方法来了
游戏·游戏引擎·虚幻·战犬·战狗·wardogs
雷电小将军14 小时前
诡秘之主手游占卜家序列加点 诡秘之主手游占卜家怎么加点
服务器·网络·游戏·电脑·玩游戏
界面开发小八哥15 小时前
界面控件DevExpress Blazor v26.1新版亮点 - Filter Builder等功能升级
ui·界面控件·blazor·devexpress·ui开发
小贺儿开发16 小时前
Unity 程序员编曲花絮:汪峰《河流》细节修复
科技·学习·unity·程序员·音乐·demo·写代码
兰亭妙微UI设计公司16 小时前
兰亭妙微UI设计:Neemo Project 企业AI项目管理后台全案运营价值解析
人工智能·ui
598866753@qq.com17 小时前
Unity UniText笔记
unity·unitext
杨丰玮41817 小时前
从零手写Java飞机躲障碍游戏|Swing绘图、鼠标跟随、计时器碰撞检测实战(五)
java·python·游戏·游戏引擎·图形渲染·动画·贴图
yangmu320318 小时前
《明末:渊虚之羽》1.7版百款MOD终极指南:硬核配置与保姆级安装教程
游戏
鼎艺创新科技1 天前
不依赖 UE/Unity:我们如何从零搭建一套国产三维 GIS 渲染引擎
人工智能·算法·unity·游戏引擎·三维电子沙盘
JoyCong19981 天前
ToDesk个人版、专业版、游戏版、设计版、性能版、团队版权益介绍
大数据·运维·游戏·远程工作·远程操作