新手引导遮罩(学习笔记)

前言

本文仅当学习笔记留存。

业务情景与效果展示

玩家解锁了一个功能,此时进行一个点击按钮的引导,这个引导需要排查其它视觉影响因素,如将按钮的位置之外的地方进行压黑,同时让玩家仅可以点击交互按钮。 

 

核心代码

矩形遮罩Shader

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)
//矩形遮罩Shader

Shader "UI/Guidance/RectGuidance"
{
    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
            };

            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;

            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 = v.texcoord;

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

            sampler2D _MainTex;

            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
        }
    }
}

 矩形遮罩核心脚本

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

/// <summary>
/// 矩形引导组件
/// </summary>
public class RectGuidanceController : MonoBehaviour, ICanvasRaycastFilter {
    //获取画布
    public Canvas canvas;

    /// <summary>
    /// 高亮显示的目标
    /// </summary>
    public RectTransform target;

    /// <summary>
    /// 区域范围缓存
    /// </summary>
    private Vector3[] corners = new Vector3[4];

    /// <summary>
    /// 镂空区域中心
    /// </summary>
    private Vector4 center;

    /// <summary>
    /// 最终的偏移值X
    /// </summary>
    private float targetOffsetX = 0f;

    /// <summary>
    /// 最终的偏移值Y
    /// </summary>
    private float targetOffsetY = 0f;

    /// <summary>
    /// 遮罩材质
    /// </summary>
    private Material material;

    /// <summary>
    /// 当前的偏移值X
    /// </summary>
    private float currentOffsetX = 0f;

    /// <summary>
    /// 当前的偏移值Y
    /// </summary>
    private float currentOffsetY = 0f;

    /// <summary>
    /// 动画收缩时间
    /// </summary>
    public float shrinkTime = 0.5f;


    private float shrinkVelocityX = 0f;
    private float shrinkVelocityY = 0f;

    /// <summary>
    /// 世界坐标到画布坐标的转换
    /// </summary>
    /// <param name="canvas">画布</param>
    /// <param name="world">世界坐标</param>
    /// <returns>转换后在画布的坐标</returns>
    private Vector2 WorldToCanvasPos(Canvas canvas, Vector3 world) {
        Vector2 position;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.transform as RectTransform, world,
            canvas.GetComponent<Camera>(), out position);
        return position;
    }

    public void SetTarget(RectTransform target) {
        this.target = target;
        RefreshMask();
    }

    public void RefreshMask() {
        //获取高亮区域四个顶点的世界坐标
        target.GetWorldCorners(corners);
        //计算高亮显示区域咋画布中的范围
        targetOffsetX = Vector2.Distance(WorldToCanvasPos(canvas, corners[0]), WorldToCanvasPos(canvas, corners[3])) / 2f;
        targetOffsetY = Vector2.Distance(WorldToCanvasPos(canvas, corners[0]), WorldToCanvasPos(canvas, corners[1])) / 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 centerWorld = new Vector3(x, y, 0);
        Vector2 center = WorldToCanvasPos(canvas, centerWorld);
        //设置遮罩材料中中心变量
        Vector4 centerMat = new Vector4(center.x, center.y, 0, 0);
        material = GetComponent<Image>().material;
        material.SetVector("_Center", centerMat);
        //计算当前偏移的初始值
        RectTransform canvasRectTransform = (canvas.transform as RectTransform);
        if (canvasRectTransform != null) {
            //获取画布区域的四个顶点
            canvasRectTransform.GetWorldCorners(corners);
            //求偏移初始值
            for (int i = 0; i < corners.Length; i++) {
                if (i % 2 == 0)
                    currentOffsetX = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corners[i]), center), currentOffsetX);
                else
                    currentOffsetY = Mathf.Max(Vector3.Distance(WorldToCanvasPos(canvas, corners[i]), center), currentOffsetY);
            }
        }
        //设置遮罩材质中当前偏移的变量
        material.SetFloat("_SliderX", currentOffsetX);
        material.SetFloat("_SliderY", currentOffsetY);
    }

    private void Update() {
        //从当前偏移值到目标偏移值差值显示收缩动画
        float valueX = Mathf.SmoothDamp(currentOffsetX, targetOffsetX, ref shrinkVelocityX, shrinkTime);
        float valueY = Mathf.SmoothDamp(currentOffsetY, targetOffsetY, ref shrinkVelocityY, shrinkTime);
        if (!Mathf.Approximately(valueX, currentOffsetX)) {
            currentOffsetX = valueX;
            material.SetFloat("_SliderX", currentOffsetX);
        }

        if (!Mathf.Approximately(valueY, currentOffsetY)) {
            currentOffsetY = valueY;
            material.SetFloat("_SliderY", currentOffsetY);
        }
    }

    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) {
        if (target == null) return false;
        bool InRect = RectTransformUtility.RectangleContainsScreenPoint(target.GetComponent<RectTransform>(), sp, eventCamera);
        if (InRect)
            Debug.Log("点击可以穿透并能点中按钮");
        else
            Debug.LogWarning("点不中按钮的,死心吧");
        return !InRect;
    }
}

重点方法:

这个功能中需要能点击没有被遮罩盖住的按钮,但被遮罩的地方不能被点击

先介绍两个unity的方法

  • IsRaycastLocationValid

需要接口ICanvasRaycastFilter

它的作用是:给定一个点和一个摄像机,判断射线投射是否有效。返回 false 则这个物体将不会被射线击中。

  • RectTransform.RectangleContainsScreenPoint

 是 Unity 中 RectTransform 类的一个方法,用于判断一个屏幕空间下的点是否在该 RectTransform 所代表的矩形内。

将两个方法结合起来就是

//点击穿透
    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) {
        if (target == nullreturn false;
            bool InRect = RectTransformUtility.RectangleContainsScreenPoint(target.GetComponent<RectTransform>(), sp, eventCamera);
        if (InRect)
            Debug.Log("点击可以穿透并能点中按钮");
        else
            Debug.LogWarning("点不中按钮的,死心吧");

        //取反是因为IsRaycastLocationValid针对的是当前挂载的对象,也就是ImgMask,返回 false 则ImgMask将不会被射线击中
        return !InRect;
    }
}

注意

RectTransformUtility.RectangleContainsScreenPoint使用时

当场景中的Canvas为Screen Space -Overlay时:

RectTransformUtility.RectangleContainsScreenPoint(target.GetComponent<RectTransform>(), sp);
这样写是正常的

但当你场景中的Canvas为Screen Space -Camera时:
仅传两个参数时始终都返回 False,需要和我那样使用三个参数才可以正常使用


圆形遮罩Shader

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "UI/Guidance/CircleGuidance"
{
    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,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
            };

            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            float4 _ClipRect;
            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 = v.texcoord;

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

            sampler2D _MainTex;

            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
        }
    }
}

圆形遮罩核心脚本

 
/// <summary>
/// 圆形遮罩镂空引导
/// </summary>
public class CircleGuidanceController : MonoBehaviour, ICanvasRaycastFilter {
    public Canvas canvas;

    /// <summary>
    /// 镂空区域最终半径
    /// </summary>
    public float radius = 200f;

    /// <summary>
    /// 镂空区域初始半径
    /// </summary>
    public float initialRadius = 600f;

    /// <summary>
    /// 当前镂空区域的半径
    /// </summary>
    private float currentRadius = 600f;

    /// <summary>
    /// 镂空区域圆心
    /// </summary>
    Vector2 center;

    /// <summary>
    /// 镂空区域圆心屏幕坐标
    /// </summary>
    Vector2 targetScreenPos;

    /// <summary>
    /// 遮罩材质
    /// </summary>
    private Material material;


    /// <summary>
    /// 高亮区域缩放的动画时间
    /// </summary>
    public float shrinkTime = 0.5f;

    /// <summary>
    /// 收缩速度
    /// </summary>
    float shrinkVelocity = 0f;

    /// <summary>
    /// 世界坐标向画布坐标转换
    /// </summary>
    /// <param name="canvas">画布</param>
    /// <param name="world">世界坐标</param>
    /// <returns>返回画布上的二维坐标</returns>
    private Vector2 ScreenToCanvasPos(Canvas canvas, Vector3 world)
    {
        Vector2 position;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas.GetComponent<RectTransform>(),
            world, UIManager.Instance.UICamera, out position);
        return position;
    }

    public void RefreshMask(Vector3 targetScreenPos)
    {
        this.targetScreenPos = targetScreenPos;
        center = ScreenToCanvasPos(canvas, this.targetScreenPos);
        //设置遮罩材料中的圆心变量
        Vector4 centerMat = new Vector4(center.x, center.y, 0, 0);
        material = GetComponent<Image>().material;
        currentRadius = initialRadius;
        material.SetVector("_Center", centerMat);
        material.SetFloat("_Slider", currentRadius);
    }

    private void Update()
    {
        //从当前半径到目标半径差值显示收缩动画
        float value = Mathf.SmoothDamp(currentRadius, radius, ref shrinkVelocity, shrinkTime);
        if (!Mathf.Approximately(value, currentRadius))
        {
            currentRadius = value;
            material.SetFloat("_Slider", currentRadius);
        }
    }

    public bool IsRaycastLocationValid(Vector2 sp, Camera eventCamera) {
        return InCircle(sp);
    }

    bool InCircle(Vector2 sp) {
        float distanceToCenter = Vector2.Distance(this.targetScreenPos, sp);
        // 判断距离是否小于等于半径
        if (distanceToCenter <= radius) {
            Debug.Log("点击位置在圆内");
        } else {
            Debug.LogWarning("点击位置不在圆内");
        }
        return distanceToCenter <= radius;

    }
} 

圆形遮罩需要调用RefreshMask(Vector3 targetScreenPos)方法来启用,传入的参数是屏幕坐标位置,即圆心位置。

 使用方式

1.创建一个全撑的Image

2.设置Image的材质

3.挂载脚本并绑定需要的节点

当然,上面的仅仅是基础的效果展示,具体的使用需要根据业务场景进行调整!!!


大佬的链接

unity游戏开发中的新手引导框架(一) - 知乎

Unity游戏开发中的新手引导框架(二) - 知乎

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值