Unity新手引导(圆形指引、矩形指引)

声明:中心镂空为圆形或矩形,增加指引动画,基于UGUI

一、Shader

建立两个shader,命名为RectGuide、CircleGuide。

  • RectGuide
Shader "UI/RectGuide"
{
    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

        _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"

                #pragma multi_compile __ UNITY_UI_CLIP_RECT
                #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
                };

                sampler2D _MainTex;
                fixed4 _Color;
                fixed4 _TextureSampleAdd;
                float4 _ClipRect;
                float4 _MainTex_ST;

                float2 _Center;
                float _SliderX;
                float _SliderY;

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

                    OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

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

                fixed4 frag(v2f IN) : SV_Target
                {
                    half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                    #ifdef UNITY_UI_CLIP_RECT
                    color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                    #endif

                    #ifdef UNITY_UI_ALPHACLIP
                    clip(color.a - 0.001);
                    #endif
                    float2 dis = IN.worldPosition.xy - _Center.xy;
                    color.a *= (abs(dis.x) > _SliderX) || (abs(dis.y) > _SliderY);
                    color.rgb *= color.a;

                    return color;
                }
            ENDCG
            }
        }
}
  • CircleGuide
Shader "UI/CircleGuide"
{
    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
        _Center("Center",vector) = (0,0,0,0)
        _Slider("Slider",Range(0,2500)) = 2500
    }

        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_CLIP_RECT
                #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
                };

                sampler2D _MainTex;
                fixed4 _Color;
                fixed4 _TextureSampleAdd;
                float4 _ClipRect;
                float4 _MainTex_ST;
                float2 _Center;
                float _Slider;

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

                    OUT.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);

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

                fixed4 frag(v2f IN) : SV_Target
                {
                    half4 color = (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;

                    #ifdef UNITY_UI_CLIP_RECT
                    color.a *= UnityGet2DClipping(IN.worldPosition.xy, _ClipRect);
                    #endif

                    #ifdef UNITY_UI_ALPHACLIP
                    clip(color.a - 0.001);
                    #endif
                    color.a *= (distance(IN.worldPosition.xy,_Center.xy) > _Slider);
                    color.rgb *= color.a;

                    return color;
                }
            ENDCG
            }
        }
}

二、前期准备

  • 建立两个材质球,命名为rectMat、circleMat,将对应shader赋值给材质。

在这里插入图片描述

  • 创建UI-Image,将Image填充满屏幕,将其中一个材质赋值给Image上的material,修改Image的透明度,一般为纯黑半透明。

在这里插入图片描述

三、脚本实现

  • 创建基类GuideBase脚本,用来管理两个shader,因为中心镂空的效果,除了半 径或者长宽的计算方式不一样,其他的都一样。
using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Image))]
public class GuideBase : MonoBehaviour
{
    protected Material material;//材质
    protected Vector3 center;//镂空中心
    protected RectTransform target;//被引导的目标对象
    protected Vector3[] targetCorners = new Vector3[4];//引导目标的边界

    protected float timer;//计时器,来达到动画匀速播放
    protected float time;//整体动画时间
    protected bool isScaling;//是否正在缩放
   //虚方法,子类可以去重写,里面用来判断动画是否播放,如果播放,就按照既定的时间匀速完成
    protected virtual void Update()
    {
        if (isScaling)
        {
            timer += Time.deltaTime * 1 / time;
            if (timer >= 1)
            {
                timer = 0;
                isScaling = false;
            }
        }
    }
   //这里是来获取目标物体的四个点来计算中心点,因为对于矩形或者圆形效果,他们面对的中心点是确定的
    public virtual void Guide(Canvas canvas, RectTransform target)
    {
        material = GetComponent<Image>().material;
        this.target = target;
        //获取四个点的世界坐标
        target.GetWorldCorners(targetCorners);
        //世界坐标转屏幕坐标
        for (int i = 0; i < targetCorners.Length; i++)
        {
            targetCorners[i] = WorldToScreenPoints(canvas, targetCorners[i]);
        }
        //计算中心点
        center.x = targetCorners[0].x + (targetCorners[3].x - targetCorners[0].x) / 2;
        center.y = targetCorners[0].y + (targetCorners[1].y - targetCorners[0].y) / 2;
        //设置中心点
        material.SetVector("_Center", center);
    }
   //为了让子类继承的时候直接重写就可以,因为矩形和圆形的动画方式不一样,跟长宽或者半径有关
    public virtual void Guide(Canvas canvas, RectTransform target,float scale,float time)
    {

    }
   //坐标的转换
    public Vector2 WorldToScreenPoints(Canvas canvas, Vector3 world)
    {
        //把世界转屏幕
        Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(canvas.worldCamera, world);
        Vector2 localPoint;
        //屏幕转局部坐标
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.GetComponent<RectTransform>(), screenPoint, canvas.worldCamera, out localPoint);
        return localPoint;
    }
}
  • 创建CircleGuide和RectGuide脚本来重写基类GuideBase脚本里的通用方法。
  • CircleGuide脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CircleGuide : GuideBase
{
    private float r;//镂空半径
    private float scaleR;//变化之后的半径大小

   //继承GuideBase基类,重写他的获取目标位置同时修改半径的方法
    public override void Guide(Canvas canvas, RectTransform target)
    {
        base.Guide(canvas, target);//继承基类里面获取中心点的计算
        //计算半径
        float width = (targetCorners[3].x - targetCorners[0].x) / 2;
        float height = (targetCorners[1].y - targetCorners[0].y) / 2;
        r=Mathf.Sqrt(width*width+height*height);
        this.material.SetFloat("_Slider", r);
    }
    //重写基类动画方法,获取半径值来达到动画效果
    public override void Guide(Canvas canvas, RectTransform target, float scale, float time)
    {
        this.Guide(canvas, target);//需要中心点,所以直接调用上一个方法
        scaleR = r * scale;
        this.material.SetFloat("_Slider", scaleR);

        this.time = time;
        isScaling = true;
        timer = 0;
    }

    protected override void Update()
    {
        base.Update();
        if (isScaling)
        {
            this.material.SetFloat("_Slider", Mathf.Lerp(scaleR,r,timer));
        }
    }

}
  • RectGuide脚本
using UnityEngine;
using UnityEngine.UI;

public class RectGuide : GuideBase
{
    protected float width;//镂空宽
    protected float height;//镂空高

    float scalewidth;
    float scaleheight;
    public override void Guide(Canvas canvas,RectTransform target)
    {
        base.Guide(canvas, target);
        //计算宽高
        width = (targetCorners[3].x - targetCorners[0].x) / 2;
        height = (targetCorners[1].y - targetCorners[0].y) / 2;
        material.SetFloat("_SliderX", width);
        material.SetFloat("_SliderY", height);
    }

    public override void Guide(Canvas canvas, RectTransform target,float scale,float time)
    {
        this.Guide(canvas, target);

        scalewidth = width * scale;
        scaleheight = height * scale;
        material.SetFloat("_SliderX", scalewidth);
        material.SetFloat("_SliderY", scaleheight);

        this.time = time;
        isScaling = true;
        timer = 0;
        
    }

    protected override void Update()
    {
        base.Update();
        if (isScaling)
        {
            this.material.SetFloat("_SliderX", Mathf.Lerp(scalewidth, width, timer));
            this.material.SetFloat("_SliderY", Mathf.Lerp(scaleheight, height, timer));
        }
    }
}
  • 创建GuideController脚本来实现方法被调用,从而达到相应效果。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public enum GuideType
{
    Rect,
    Circle,
}

[RequireComponent(typeof(CircleGuide))]
[RequireComponent(typeof(RectGuide))]
public class GuideController : MonoBehaviour,ICanvasRaycastFilter
{
    private CircleGuide circleGuide;
    private RectGuide rectGuide;

    public Material rectMat;
    public Material circleMat;
    private Image mask;//就是本身,纯黑半透明
    private RectTransform target;
    private void Awake()
    {
        mask = transform.GetComponent<Image>();
        if (mask == null) { throw new System.Exception("mask初始化失败"); }
        if (rectMat == null || circleMat == null) { throw new System.Exception("材质未赋值"); }
        rectGuide = transform.GetComponent<RectGuide>();
        circleGuide = transform.GetComponent<CircleGuide>();
    }

    public void Guide(Canvas canvas,RectTransform target,GuideType guideType)
    {
        this.target = target;
        switch (guideType)
        {
            case GuideType.Rect:
                mask.material = rectMat;
                rectGuide.Guide(canvas, target);
                break;
            case GuideType.Circle:
                mask.material = circleMat;
                circleGuide.Guide(canvas, target);
                break;

        }
    }

    public void Guide(Canvas canvas, RectTransform target, GuideType guideType,float scale,float time)
    {
        this.target = target;
        switch (guideType)
        {
            case GuideType.Rect:
                mask.material = rectMat;
                rectGuide.Guide(canvas, target, scale,time);
                break;
            case GuideType.Circle:
                mask.material = circleMat;
                circleGuide.Guide(canvas, target, scale, time);
                break;

        }
    }
//这里的方法代表是否镂空内容可被点击,返回false则可以,true则不可以
    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera)
    {
        if (target==null) { return true; }
        return !RectTransformUtility.RectangleContainsScreenPoint(target, sp);
    }
}

四、测试

  1. 在Image上挂载RectGuide、CircleGuide、GuideController脚本,同时创建测试脚本GuidePanel并挂载到Image身上。

在这里插入图片描述

  1. GuidePanel的实现
using UnityEngine;

public class GuidePanel : MonoBehaviour
{
    GuideController guideController;
    Canvas canvas;

    private void Start()
    {
        canvas = transform.GetComponentInParent<Canvas>();
        guideController = transform.GetComponent<GuideController>();
        //这句代码的参数代表(哪个画布,哪个对象需要被镂空引导,镂空的类型,镂空缩放动画前的比例,镂空缩放动画的时长)
        guideController.Guide(canvas, GameObject.Find("GuideUI").GetComponent<RectTransform>(), GuideType.Circle,2,0.5f);
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐