Unity3d+Newbie guide引导:读CSV表驱动,屏蔽不可点击区域,UI镂空矩形区域Shader

2023-08-24 15:17:39 浏览数 (2)

主要功能

  1. 表驱动,引导到哪步查找ui面板下路径
  2. 屏蔽不可点击区域,点击屏蔽,UImask镂空
  3. 具有点击该按钮驱动下一步,或者点击新手引导的下一步驱动

数据结构

代码语言:javascript复制
//新手引导UI箭头出现的方向
public enum EnGuideDir
{
    up = 0,
    down = 1,
    left = 2,
    right = 3  
}

//出现引导如何跳转下一步
public enum EnGuideClick
{
    NoClickCloseSelf = 0, //点击空白处关闭当前ui面板
    Click = 1, //点击要引导的按个按钮
    NoClickNoClose = 2, //点击空白处,只关闭引导mask,不关闭UI面板
    ClickNeedNext = 3, // 可以点击但是要通过点击 "下一步"按钮  驱动 ,针对输入框
}

[System.Serializable]
public class NewGuideItem
{
    public string panelName; //面板的名字
    public string imgPath; //目标的路径
    public string text;//提示的字
    public EnGuideClick isCanClick = EnGuideClick.Click; // 目标按钮可点击 1:可点  0:不可点,并关闭自己   2:不可点,不关闭自己
    public int isTextShowDir = -1; // 文本显示按钮的位置 -1 为下, 1为上   ,  2   
    public int belongCanvas = 0; // 属于哪个ui canvas下   0:screen  1:top
    public string bgPath; //  背景路径,新手引导的收缩至此,即这个区域是可点击区域,其他区域半透明黑色,屏蔽点击
    public int isAutoNext = 1; // 是否自动开始下步引导   0:不自动  1:自动
    public string param = ""; // 传入参数
}

策划用数据表

设置引导到第几步,开启引导遮罩

代码语言:javascript复制
/// <summary>
    /// 查找当前界面 是否 是当前的新手引导的第n步,如果找到了,执行引导遮罩
    /// </summary>
    /// <param name="delay"></param>
    /// <returns></returns>
    IEnumerator YieldDoNextNewGuide(int delay)
    {
        yield return delay;
        //DoNextNewGuide();

        NewGuideItem item = m_listGuide[m_curIdx];
        Transform transCanvas = null;
        if (item.belongCanvas == 0)
        {
            transCanvas = m_canvasScreen;
            Debug.Log("transCanvas:"   transCanvas.name);
        }
        else if (item.belongCanvas == 1)
        {
            transCanvas = m_canvasTop;
            Debug.Log("transCanvas:"   transCanvas.name);
        }
        Transform trans = null;

        while (trans == null)
        {
            //Debug.Log("接着找");
            yield return new WaitForSeconds(1);
            try
            {
                Debug.Log("新手引导查找:"   item.panelName   "/"   item.imgPath);
                trans = transCanvas.Find(item.panelName   "/"   item.imgPath);
            }
            catch
            {
                Debug.Log("新手引导找不到:"   item.panelName   "/"   item.imgPath);
            }
        }

        UGUIPanel panel = transCanvas.Find(item.panelName).GetComponent<UGUIPanel>();

        while (panel.m_isOpen == false)
        {
            yield return null;
        }
        trans.gameObject.AddComponent<DontDrag>(); //如果引导在滚动层上,加屏蔽滚动

        //目标本身可点,击且点击后能驱动到下一步引导,m_curIdx 1,并接着引导
        if (item.isCanClick == EnGuideClick.Click && item.isAutoNext == 1)
        {
            while (trans.gameObject.GetComponent<ClickListener>() == null)
            {
                yield return null;
            }
            trans.gameObject.GetComponent<ClickListener>().onNewGuideClick = (obj) =>
            {
                StartOneNewGuide(); 
                trans.gameObject.GetComponent<ClickListener>().onNewGuideClick = null;
            };
        }

        //目标本身可点击,点击后不能驱动下一步,新手引导暂停
        if (item.isCanClick == EnGuideClick.Click && item.isAutoNext == 0)
        {
            while (trans.gameObject.GetComponent<ClickListener>() == null)
            {
                yield return null;
            }
            trans.gameObject.GetComponent<ClickListener>().onNewGuideClick = (obj) =>
            {
                newguidepanel.Instance.PauseGuide();
                trans.gameObject.GetComponent<ClickListener>().onNewGuideClick = null;
            };
        }

        m_lastTarget = trans;
        Image img = trans.GetComponent<Image>();
        Transform transBg = transCanvas.Find(item.panelName   "/"   item.bgPath);
        Image imgBg = transBg.GetComponent<Image>();
        newguidepanel.Instance.Open(img,imgBg,item);
        
        ReqUpdateOtherDataMessage req = new ReqUpdateOtherDataMessage();
        req.fieldName = "newStep";
        req.value = m_curIdx.ToString();
        HallSocket.Instance.SendMsgProto(MsgIdDefine.ReqUpdateOtherDataMessage, req);
        m_curIdx  ;

    }
  1. 协程中一直查找当前步骤要引导的界面,目标(界面上的按钮或者图,用UImask 包裹的区域,能驱动下一步)
  2. 目标本身可点,击且点击后能驱动到下一步引导,m_curIdx 1,并接着引导
  3. 目标本身可点击,点击后不能驱动下一步,新手引导暂停
  4. 如果引导目标是在滚动层上,需要加上禁止滚动脚本,防止滚没了目标

设置遮罩

遮罩继承ICanvasRaycastFilter 该元素可以过滤射线投射。如果顶级元素被命中,它还能进一步“检查”该位置是否有效。

  1. 传入shader的参数:目标的中心点
  2. 传入shader的参数:目标的离中心点最大长度的一半
  3. 传入shader的参数:目标的离中心点最大高度的一半
代码语言:javascript复制
void SetNewTargetImage()
    {
        //获取画布
        //Canvas m_canvas = GameObject.Find("Canvas").GetComponent<Canvas>();
        //Canvas m_canvas = GameObject.Find("Canvas").GetComponent<Canvas>();
        //获取高亮区域四个顶点的世界坐标
        
        m_bgTarget.rectTransform.GetWorldCorners(_corners);
        //计算高亮显示区域咋画布中的范围
        _targetOffsetX = Vector2.Distance(WorldToCanvasPos(m_canvas, _corners[0]), WorldToCanvasPos(m_canvas, _corners[3])) / 2f;
        _targetOffsetY = Vector2.Distance(WorldToCanvasPos(m_canvas, _corners[0]), WorldToCanvasPos(m_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(m_canvas, centerWorld);
        //设置遮罩材料中中心变量
        Vector4 centerMat = new Vector4(center.x, center.y, 0, 0);
        _material = GetComponent<Image>().material;
        _material.SetVector("_Center", centerMat); //传入要镂空矩形的中心点
        //计算当前偏移的初始值
        RectTransform canvasRectTransform = (m_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(m_canvas, _corners[i]), center), _currentOffsetX);
                else
                    _currentOffsetY = Mathf.Max(Vector3.Distance(WorldToCanvasPos(m_canvas, _corners[i]), center), _currentOffsetY);
            }
        }
        //设置遮罩材质中当前偏移的变量
        _material.SetFloat("_SliderX", _currentOffsetX);//设置离中心点最大的x距离
        _material.SetFloat("_SliderY", _currentOffsetY);//设置离中心点最大的y距离
        m_isSetOk = true;
    }

遮罩裁剪shader

代码语言:javascript复制
// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

Shader "UI/BeginnerGuidance/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

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

float2 dis = IN.worldPosition.xy - _Center.xy; color.a *= (abs(dis.x) > _SliderX) || (abs(dis.y) > _SliderY); 以中心点_Center.xy,最大_SliderX的宽度,和最大_SliderY高度内,color.a = 0,达到镂空效果

源码

https://github.com/luoyikun/VirtualCity TestNewGuide场景 NewGuideDemo.m_isNewGuideDemo这个参数要为true(单独测试用引动用)

demo演示

0 人点赞