uGUI学习篇: 新手引导镂空遮罩与事件透传
前言
新手引导我们经常需要制作这样的效果:全屏有个中间镂空的遮罩,引导玩家点击遮罩镂空的部位的按钮。
在Unity中如何实现这个效果呢,以uGUI的方式为例,跟着下面的步骤开始吧
第一步:shader脚本
创建一个GuideMask.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
[Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0
//-------------------add----------------------
_Center("Center", vector) = (0, 0, 0, 0)
_Silder ("_Silder", Range (0,1000)) = 1000 // sliders
//-------------------add----------------------
}
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;
//-------------------add----------------------
float _Silder;
float2 _Center;
//-------------------add----------------------
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
{
half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
#ifdef UNITY_UI_ALPHACLIP
clip (color.a - 0.001);
#endif
//-------------------add----------------------
color.a*=(distance(IN.worldPosition.xy,_Center.xy) > _Silder);
color.rgb*= color.a;
//-------------------add----------------------
return color;
}
ENDCG
}
}
}
第二步:创建材质球
新建一个材质球,命名为GuideMask.material,并使用上面的shader
第三步:C#脚本
创建一个Guide.cs脚本,实现新手引导的镂空遮罩动画,代码如下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 新手引导动画
/// </summary>
[RequireComponent(type(EventPermeate))]
public class Guide : MonoBehaviour
{
public Image target;
private Material material;
private float diameter; // 直径
private float current = 0f;
Vector3[] corners = new Vector3[4];
void Awake ()
{
// 设置事件透传对象
gameObject.GetComponent<EventPermeate>.target = target.gameObject;
Canvas canvas = GameObject.Find ("Canvas").GetComponent<Canvas> ();
target.rectTransform.GetWorldCorners (corners);
diameter = Vector2.Distance (WordToCanvasPos(canvas,corners [0]), WordToCanvasPos(canvas,corners [2])) / 2f;
float x =corners [0].x + ((corners [3].x - corners [0].x) / 2f);
float y =corners [0].y + ((corners [1].y - corners [0].y) / 2f);
Vector3 center = new Vector3 (x, y, 0f);
Vector2 position = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, center, canvas.GetComponent<Camera>(), out position);
center = new Vector4 (position.x,position.y,0f,0f);
material = GetComponent<Image>().material;
material.SetVector ("_Center", center);
(canvas.transform as RectTransform).GetWorldCorners (corners);
for (int i = 0; i < corners.Length; i++)
{
current = Mathf.Max(Vector3.Distance (WordToCanvasPos(canvas,corners [i]), center),current);
}
material.SetFloat ("_Silder", current);
}
float yVelocity = 0f;
void Update ()
{
float value = Mathf.SmoothDamp(current, diameter, ref yVelocity, 0.3f);
if (!Mathf.Approximately (value, current)) {
current = value;
material.SetFloat ("_Silder", current);
}
}
void OnGUI()
{
if(GUILayout.Button("Test"))
{
Awake ();
}
}
Vector2 WordToCanvasPos(Canvas canvas,Vector3 world)
{
Vector2 position = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, world, canvas.GetComponent<Camera>(), out position);
return position;
}
}
创建一个EventPermeate.cs脚本,实现事件穿透,代码如下:
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System.Collections.Generic;
public class EventPermeate: MonoBehaviour, IPointerClickHandler, IPointerDownHandler, IPointerUpHandler
{
// 事件穿透对象
[HideInInspector]
public GameObject target;
// 监听按下
public void OnPointerDown(PointerEventData eventData)
{
PassEvent(eventData,ExecuteEvents.pointerDownHandler);
}
// 监听抬起
public void OnPointerUp(PointerEventData eventData)
{
PassEvent(eventData,ExecuteEvents.pointerUpHandler);
}
// 监听点击
public void OnPointerClick(PointerEventData eventData)
{
PassEvent(eventData,ExecuteEvents.submitHandler);
PassEvent(eventData,ExecuteEvents.pointerClickHandler);
}
// 把事件透下去
public void PassEvent<T>(PointerEventData data,ExecuteEvents.EventFunction<T> function)
where T : IEventSystemHandler
{
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(data, results);
GameObject current = data.pointerCurrentRaycast.gameObject ;
for(int i =0; i< results.Count;i++)
{
if(target == results[i].gameObject)
{
// 如果是目标物体,则把事件透传下去,然后break
ExecuteEvents.Execute(results[i].gameObject, data,function);
break;
}
}
}
}
第四步:创建ui进行测试
1 在Canvas创建两个按钮Button1和Button2
2 创建一个Image,命名为guide_mask,设置Width和Height使其可以遮住全屏,为其添加Guide组件,此时会自动添加EventPermeat组件
3 设置guide_mask的Image组件的Color为半透明黑色,设置Material属性为我们上面制作的材质球GuideMask.mat
4 把Button1对象赋值给guide_mask的Guide组件的Target属性
运行效果
想要引导点击哪个按钮,就把Guide组件的Target属性设置为那个按钮即可