Unity最简单引导实现方式
转载自 Unity最简单引导实现方式
并去掉了一些无用的,混淆的shader代码,调整了代码结构。
效果
镂空shader代码
Shader "UI/Guide/Rect"
{
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
_Center("Center",vector) = (0,0,0,0)
_SliderX("SliderX",Range(0,1500)) = 1500
_SliderY("SliderY",Range(0,1500)) = 1500
}
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"
struct appdata_t
{
float4 vertex : POSITION;
float4 color : COLOR;
float2 texcoord : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 positionCS : SV_POSITION;
fixed4 color : COLOR;
float2 texcoord : TEXCOORD0;
float4 positionOS : TEXCOORD1;
UNITY_VERTEX_OUTPUT_STEREO
};
fixed4 _Color;
float4 _ClipRect;
float2 _Center;
float _SliderX;
float _SliderY;
v2f vert(appdata_t v)
{
v2f output;
output.positionOS = v.vertex;
output.positionCS = UnityObjectToClipPos(output.positionOS);
output.texcoord = v.texcoord;
output.color = v.color * _Color;
return output;
}
sampler2D _MainTex;
fixed4 frag(v2f input) : SV_Target
{
half4 color = tex2D(_MainTex, input.texcoord) * input.color;
float2 dis = input.positionOS.xy - _Center.xy;
color.a *= (abs(dis.x) > _SliderX) || (abs(dis.y) > _SliderY);
color.rgb *= color.a;
return color;
}
ENDCG
}
}
}
控制C#代码
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Image))]
public class GuideController : MonoBehaviour, ICanvasRaycastFilter
{
private Material m_Material;
private Vector3 m_Center;
private RectTransform m_Target;
private Vector3[] m_TargetCorners = new Vector3[4];
private Camera UICamera;
private float m_Timer;
private float m_Time;
private bool m_IsScaling;
private float m_Width;
private float m_Height;
private float m_ScaleWidth;
private float m_ScaleHeight;
private RectTransform m_RectTransform;
// 弱引导整个传递下去 强引导传递Target范围内的事件
private bool m_IsSoftGuide;
private void Awake()
{
UICamera = GameObject.Find("Main Camera").GetComponent<Camera>();
m_Material = GetComponent<Image>().material;
m_RectTransform = GetComponent<RectTransform>();
}
protected virtual void Update()
{
if (m_IsScaling)
{
m_Timer += Time.deltaTime / m_Time;
m_Timer = Mathf.Clamp(m_Timer, 0, 1);
m_Material.SetFloat("_SliderX", Mathf.Lerp(m_ScaleWidth, m_Width, m_Timer));
m_Material.SetFloat("_SliderY", Mathf.Lerp(m_ScaleHeight, m_Height, m_Timer));
if (m_Timer >= 1)
{
m_Timer = 0;
m_IsScaling = false;
}
}
}
// 这里是来获取目标物体的四个点来计算中心点,因为对于矩形或者圆形效果,他们面对的中心点是确定的
public virtual void Guide(RectTransform target, bool isSoftGuide, float scale = 1, float time = 0)
{
if (target == null)
return;
m_Target = target;
m_IsSoftGuide = isSoftGuide;
target.GetWorldCorners(m_TargetCorners);
for (int i = 0; i < m_TargetCorners.Length; i++)
{
m_TargetCorners[i] = WorldToScreenPoints(m_TargetCorners[i]);
}
// 中心点
m_Center.x = m_TargetCorners[0].x + (m_TargetCorners[3].x - m_TargetCorners[0].x) / 2;
m_Center.y = m_TargetCorners[0].y + (m_TargetCorners[1].y - m_TargetCorners[0].y) / 2;
m_Material.SetVector("_Center", m_Center);
m_Width = (m_TargetCorners[3].x - m_TargetCorners[0].x) / 2;
m_Height = (m_TargetCorners[1].y - m_TargetCorners[0].y) / 2;
if (scale <= 1)
scale = 1;
m_ScaleWidth = m_Width * scale;
m_ScaleHeight = m_Height * scale;
m_Material.SetFloat("_SliderX", m_ScaleWidth);
m_Material.SetFloat("_SliderY", m_ScaleHeight);
m_Time = time;
m_Timer = 0;
if (m_Time > 0)
m_IsScaling = true;
}
public Vector2 WorldToScreenPoints(Vector3 world)
{
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(UICamera, world);
Vector2 localPoint;
RectTransformUtility.ScreenPointToLocalPointInRectangle(m_RectTransform, screenPoint, UICamera, out localPoint);
return localPoint;
}
// RectangleContainsScreenPoint判断一个屏幕点是否在目标Rect范围内,如果在,则返回真
// IsRaycastLocationValid为true时,事件向下传递是无效的,
// 被拦截在当前UI界面,为false,则在当前界面是无效的
public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
{
if (m_IsScaling) return true;
if (m_IsSoftGuide) return false;
return !RectTransformUtility.RectangleContainsScreenPoint(m_Target, sp, eventCamera);
}
}
using UnityEngine;
using UnityEngine.UI;
public class Main : MonoBehaviour
{
public Button StartBtn;
public Button BtnA;
public Button BtnB;
public GuideController GuideController;
public RectTransform Target;
public bool IsSoft;
public float Scale;
public float Time;
private void Awake()
{
StartBtn.onClick.AddListener(() => { GuideController.Guide(Target, IsSoft, Scale, Time); });
BtnA.onClick.AddListener(() => { Debug.Log("点击事件A"); });
BtnB.onClick.AddListener(() => { Debug.Log("点击事件B"); });
}
}
示例工程