UGUI 制作新手引导,屏幕遮罩,点击穿透

新手引导已经是游戏必备的功能了,这篇帖子分享一个制作新手引导的屏幕遮黑,只显示需要点击或者提示区域的部分

1:Shader

        首先创建一个unity shader,用于控制屏幕遮黑,并吧我们需要的区域剪裁为透明,先上源码

Shader "UI/GuideMask"
{
	Properties
	{
		[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
		_Color("Tint", Color) = (1,1,1,1)

		_StencilComp("Stencil Comparison", Float) = 8
		_Stencil("Stencil ID", Float) = 0
		_StencilOp("Stencil Operation", Float) = 0
		_StencilWriteMask("Stencil Write Mask", Float) = 255
		_StencilReadMask("Stencil Read Mask", Float) = 255

		_ColorMask("Color Mask", Float) = 15
		 
		_Origin("Origin Center",Vector) = (0,0,0,0)

		[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip("Use Alpha Clip", Float) = 0
	}

	SubShader
	{
		Tags
		{
			"Queue" = "Transparent"
			"IgnoreProjector" = "True"
			"RenderType" = "Transparent"
			"PreviewType" = "Plane"
			"CanUseSpriteAtlas" = "True"
		}

		Stencil
		{
			Ref[_Stencil]
			Comp[_StencilComp]
			Pass[_StencilOp]
			ReadMask[_StencilReadMask]
			WriteMask[_StencilWriteMask]
		}

		Cull Off
		Lighting Off
		ZWrite Off
		ZTest[unity_GUIZTestMode]
		Blend SrcAlpha OneMinusSrcAlpha
		ColorMask[_ColorMask]

		Pass
		{
			Name "Default"
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma target 2.0

			#include "UnityCG.cginc"
			#include "UnityUI.cginc"

			#pragma multi_compile __ UNITY_UI_ALPHACLIP

			struct appdata_t
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct v2f
			{
				float4 vertex : SV_POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				float4 worldPosition : TEXCOORD1;
				UNITY_VERTEX_OUTPUT_STEREO
			};

			fixed4 _Color;
			fixed4 _TextureSampleAdd;
			float4 _ClipRect;
			float4 _Origin;

			v2f vert(appdata_t IN)
			{
				v2f OUT;
				UNITY_SETUP_INSTANCE_ID(IN);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT);
				OUT.worldPosition = IN.vertex;
				OUT.vertex = UnityObjectToClipPos(OUT.worldPosition);

				OUT.texcoord = IN.texcoord;

				OUT.color = IN.color * _Color;
				return OUT;
			}

			sampler2D _MainTex;

			fixed4 frag(v2f IN) : SV_Target
			{
				float2 uv = IN.texcoord;
				half4 col = IN.color;
				//开始裁剪
				//外部直接给坐标 宽 高 GPU计算比率
				//float posX = (_Origin.x + _ScreenParams.x  / 2) / _ScreenParams.x;
				//float posY = (_Origin.y + _ScreenParams.y / 2) / _ScreenParams.y;
				float posX = _Origin.x / _ScreenParams.x;
				float posY = _Origin.y / _ScreenParams.y;
				float2 pos = float2(posX, posY);

				//float w = _Origin.z / _ScreenParams.x / 2;
				//float h = _Origin.w / _ScreenParams.y / 2;
				//if (uv.x > pos.x - w && uv.x < pos.x + w && uv.y > pos.y - h && uv.y < pos.y + h)
				//col.a = 0;
				float w = _Origin.z / _ScreenParams.x ;
				float h = _Origin.w / _ScreenParams.y;
				if (uv.x > pos.x && uv.x < pos.x + w && uv.y > pos.y && uv.y < pos.y + h)
				col.a = 0;


				half4 color = (tex2D(_MainTex,uv) + _TextureSampleAdd) * col;
				color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
				clip(col.a);
				#ifdef UNITY_UI_ALPHACLIP
					clip(color.a - 0.001);
				#endif

				return color;
			}
			ENDCG
		}
	}
}

        这个shader主要是控制一个Vector变量,x和y 代表屏幕二维坐标,z和w代表代表镂空区域的宽和高,其余地方为遮黑

        可以看到上面的代码有一部分注释区域,现在的代码逻辑是已xy的屏幕二维坐标,向右上方镂空矩形,如果把注释的代码替换掉他下面对应的几行,则是已xy的UI坐标,向四周镂空矩形,这个看个人方案取舍就行

       然后创建一个材质球,来应用这个shader,就可以看到这些参数了

        上图框选出来的Tint为遮罩颜色,一般设置黑色加一个透明度即可,下面的Origin Center则是上面说到的坐标和宽高了

        然后在UI层创建一个Image,并使用此材质,调整一下参数,就可以在Game视图看到效果了

        上面就是看到的效果了,参数为 100,100,300,500意思就是在屏幕坐标100,100的位置镂空一个300*500的矩形,注意,是屏幕坐标系,左下角为00点的,向右上方绘制

2:C#代码

        接下来创建一个c#脚本来控制我们要镂空的区域,需要一个RectTransform组件,根据组件的位置和大小,来控制镂空区域。但这层遮罩是在UI最高层的,我们还需要处理下点击事件,让其能按到指定的按钮,先上代码

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace MD.UI
{
    public enum GuideFinishType
    {
        None, 
        Btns, //点击触发按钮
        Anywhere,//点击任意位置
    }
    public class UIGuideMask : MonoBehaviour, IPointerClickHandler
    {
        [SerializeField] RectTransform outTarget;
        [SerializeField] List<Button> canClickButton;
        [SerializeField] GuideFinishType finishType;

        [SerializeField] bool isFollow; //是否高亮跟随目标

        public event Action onClickCb; //点到目标的回调

        private Material material;
        private Vector4 outValue;
        private Camera uiCamera;

        void Awake()
        {
            Image image = GetComponent<Image>();
            if (image == null)
            {
                Debug.LogError("UIGuideMask need image");
            }
            material = image.material;
            if (material == null)
            {
                Debug.LogError("UIGuideMask need  Shader GuideMask Material ");
            }

        }


        public void SetOutTarget(RectTransform target)
        {
            if (target == null)
            {
                outTarget = null;
                gameObject.SetActive(false);
            }
            else
            {
                outTarget = target;
                gameObject.SetActive(true);
                ApplyOutMask();
            }
        }
        public void SetClickBtn(List<Button> btns)
        {
            canClickButton = btns;
        }
        public void AddClickBtn(Button btn)
        {
            if (canClickButton == null) canClickButton = new List<Button>();

            canClickButton.Add(btn);
        }
        public void Finish()
        {
            outTarget = null;
            canClickButton.Clear();
            finishType = GuideFinishType.None;

            gameObject.SetActive(false);
            onClickCb?.Invoke();
        }


        void Update()
        {
            if (isFollow)
            {
                ApplyOutMask();
            }
        }


        void ApplyOutMask()
        {
            if (outTarget != null)
            {
                if (uiCamera == null)
                {
                    uiCamera = GameObject.Find("UICamera").GetComponent<Camera>();
                }

                Vector3[] corners = new Vector3[4];
                outTarget.GetWorldCorners(corners);
                Vector3 screenPos = RectTransformUtility.WorldToScreenPoint(uiCamera, corners[0]);

                outValue.x = screenPos.x;
                outValue.y = screenPos.y;
                outValue.z = outTarget.rect.width;
                outValue.w = outTarget.rect.height;

                material.SetVector("_Origin", outValue);

            }
        }

        public void OnPointerClick(PointerEventData eventData)
        {
            if (eventData == null) return;

            if (finishType == GuideFinishType.Anywhere)
            {
                Finish();
            }
            else if (finishType == GuideFinishType.Btns )
            {
                if (canClickButton != null && canClickButton.Count > 0)
                {
                    List<RaycastResult> results = new List<RaycastResult>();
                    EventSystem.current.RaycastAll(eventData, results);

                    for (int i = 0; i < results.Count; i++)
                    {
                        for (int j = 0; j < canClickButton.Count; j++)
                        {
                            if (results[i].gameObject == canClickButton[j].gameObject)
                            {
                                ExecuteEvents.Execute(canClickButton[j].gameObject, eventData, ExecuteEvents.pointerClickHandler);
                                onClickCb?.Invoke();
                            }
                        }
                    }
                }
            }

        }
    }


}

        代码的核心点

        1)需要一个参数,RectTransform outTarget  ,此对象就是镂空区域

        2)ApplyOutMask方法,这里通过获取到 outTarget 的左下角顶点的世界坐标,并将其转为UI的屏幕坐标,在获取到组件的宽高传参给shader

        3)结束类型,这里做了一个枚举 GuideFinishType, 无,指定按钮,任意区域

        4)需要一个可接收点击事件的列表 List<Button> canClickButton,只有这些按钮可以被点击到,其余控件不接受点击事件

        5)OnPointerClick方法,这个需要继承接口类IPointerClickHandler来实现,当被点击的时候就会执行,这里面首先进行了下一步的类型判断 如果是任意区域,则直接结束,如果是指定按钮,则会获取到点击位置的左右可接受点击事件的控件,在于我们可点击按钮比对,一致的话执行按钮的点击方法并结束

        6)提供了一些公开接口供外部调用,就不多说了, 自己看吧

4:测试

        

        先创建一个脚本,把四个按钮的点击事件注册上,可以看到log是可以点击到的

        打开遮罩层,设置好需要的对象,,打开Follow,可以看到上图,遮罩和镂空效果已经生效了,结束类型选择Btns 这样button3和button4就点不到了,另外两个可以正常接收点击事件

        如果需要只镂空一个按钮的位置,把OutTarget设置为按钮就可以了

        这种做法可以适配大部分的UI了,无论OutTarget的描点怎么设置,也不需要关心它的父级位置影响

demo下载地址 【免费】新手引导Demo,屏幕遮黑镂空资源-CSDN文库icon-default.png?t=N7T8https://download.csdn.net/download/qq_37302769/88750670

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity新手引导遮罩是一种常见的界面设计元素,用于引导新手玩家了解游戏界面和操作。它通常被用于高亮显示特定的界面元素,并给予玩家相应的提示。 Unity中提供了一些内置的方法来创建新手引导遮罩。首先,开发人员可以使用UGUIUnity的用户界面系统)来创建游戏界面,之后可以添加一张透明的图片作为新手引导遮罩层。然后,可以使用Unity的2D或3D特效来绘制遮罩层所遮挡住的界面元素。这些特效可以使被遮挡的区域变得模糊或是使其颜色变暗,以突出要引导的界面元素。 在引导过程中,可以根据玩家操作的进度来动态地更新遮罩层的位置和形状。例如,当玩家点击一个按钮时,可以使遮罩层逐渐展开,直到完全显示该按钮为止。还可以使用动画效果来增强引导的效果,如淡入淡出或平滑移动的效果。 同时,为了提供更多的交互性,可以为遮罩层添加响应玩家操作的功能。比如,在遮罩层上添加一个按钮,当玩家点击该按钮时,遮罩层可以自动更新到下一个引导步骤,或者直接跳转至其他相关的功能界面。 总之,Unity新手引导遮罩是一个非常实用的功能,可以帮助开发人员创建出更具吸引力和易于理解的游戏体验。它不仅可以引导玩家了解游戏的操作流程,还可以提供更好的用户体验,使玩家更容易上手游戏
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值