新手引导已经是游戏必备的功能了,这篇帖子分享一个制作新手引导的屏幕遮黑,只显示需要点击或者提示区域的部分
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文库https://download.csdn.net/download/qq_37302769/88750670