Unity UI 镂空遮罩组件教程 - 实现教程镂空效果

本文介绍如何在Unity中使用MaskableGraphic组件实现镂空遮罩效果,并解决常见问题,例如场景中多个EventSystem导致的冲突。

代码示例:

using Skillx.Utils;
using UnityEngine;
using UnityEngine.UI;

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

        private Vector3 mTargetMin = Vector3.zero;
        private Vector3 mTargetMax = Vector3.zero;

        private bool mCanRefresh = true;
        private Transform mCacheTrans = null;

        /// <summary>
        /// 设置高亮区域的位置大小
        /// </summary>
        /// <param name="target"></param>
        public void SetTarget(RectTransform target)
        {
            if (highLightTarget == null)
            {
                return;
            }

            highLightTarget.position = target.position;
            highLightTarget.sizeDelta = target.sizeDelta;
            
            mCanRefresh = true;
            RefreshView();
        }

        private void SetTarget(Vector3 tarMin, Vector3 tarMax)
        {
            if (tarMin == mTargetMin && tarMax == mTargetMax)
                return;
            mTargetMin = tarMin;
            mTargetMax = tarMax;
            SetAllDirty();
        }

        private void RefreshView()
        {
            if (!mCanRefresh)
            {
                return;
            }
            
            mCanRefresh = false;

            if (null == highLightTarget)
            {
                SetTarget(Vector3.zero, Vector3.zero);
            }
            else
            {
                Bounds bounds = RectTransformUtility.CalculateRelativeRectTransformBounds(mCacheTrans, highLightTarget);
                SetTarget(bounds.min, bounds.max);
            }
        }

        protected override void OnPopulateMesh(VertexHelper vh)
        {
            if (mTargetMin == Vector3.zero && mTargetMax == 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(mTargetMin.x, mTargetMax.y);
            vh.AddVert(vert);
            // 5 - Inner:RT
            vert.position = new Vector3(mTargetMax.x, mTargetMax.y);
            vh.AddVert(vert);
            // 6 - Inner:RB
            vert.position = new Vector3(mTargetMax.x, mTargetMin.y);
            vh.AddVert(vert);
            // 7 - Inner:LB
            vert.position = new Vector3(mTargetMin.x, mTargetMin.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 (highLightTarget == null)
            {
                return true;
            }
            
            // 将目标对象范围内的事件镂空(使事件可以穿透)
            var isClickHighLightArea = RectTransformUtility.RectangleContainsScreenPoint(highLightTarget, screenPos, eventCamera);
            
            //点击到高亮区域时,射线可以穿透
            if (!isClickHighLightArea)
            {
                //判断下方是否有引导窗口,如果有的话,通知引导进行下一步
                var uiTutorialRoot = GoHelper.FindGameObject(transform.parent.gameObject, "TutorialUIRoot");
                var uiTutorial = uiTutorialRoot.GetComponentInChildren<UITutorial>();
                if (uiTutorial != null)
                {
                    uiTutorial.Next();
                }
            }

            return !isClickHighLightArea;
        }

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

        void Update()
        {
            mCanRefresh = true;
            RefreshView();
        }
    }
}

解决场景中多个EventSystem导致的冲突:

挂上该组件后,如果场景中存在多个EventSystem,可能会出现以下报错:

(来自t Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/GraphicRaycaster.cs:332)

这是因为每个场景只能有一个EventSystem来处理用户输入事件。

解决方法:

  1. 打开场景中的Hierarchy面板。
  2. 查找并删除多余的EventSystem对象。可以通过点击对象并按下Delete键来删除。
  3. 确保只剩下一个EventSystem对象。

如果你不确定哪个对象是EventSystem,可以在Inspector面板中查看对象的组件列表,找到包含EventSystem组件的对象。

完成以上步骤后,重新运行场景,报错应该就不再出现了。

使用说明:

  1. 将TutorialHollowOutMask组件添加到需要进行镂空效果的UI元素上。
  2. 在Inspector面板中设置highLightTarget属性,该属性对应需要被镂空遮罩的区域。

注意:

  • 该组件可以与其他UI元素(例如Button、Image等)一起使用,实现更加丰富的交互效果。
  • 如果需要实现动态镂空效果,可以在代码中动态调整highLightTarget属性的值。

应用场景:

  • 常见的教程引导效果,可以将高亮区域设置为当前需要用户操作的UI元素。
  • 自定义形状的UI遮罩效果,例如圆形、三角形等。
  • 游戏中需要实现特定区域事件穿透的场景。

总结:

通过使用MaskableGraphic组件,可以轻松地在Unity UI中实现镂空遮罩效果。在使用过程中,需要注意场景中EventSystem的数量,并根据具体需求进行调整。

Unity UI 镂空遮罩组件教程 - 实现教程镂空效果

原文地址: https://www.cveoy.top/t/topic/qeTr 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录