Bootstrap

【Unity3D小功能】Unity3D中实现超炫按钮悬停效果

推荐阅读

大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。

一、前言

先来看一下效果图:
在这里插入图片描述
感觉这种效果超炫酷,就根据大佬的文章复现了一下。

并且详细补充了一下相机、Post、材质球和Shader的处理过程,接下俩就看一下如何实现吧。

二、正文

2-1、实现思路

将鼠标光标的位置传递给Shader中,根据当前像素到像素的距离去处理像素点的颜色值。

这个动作需要拆分分为背景 + 边框两部分使用 2 个 Pass 处理。

Background Pass 背景

fixed4 frag (v2f i) : SV_Target
{
    fixed4 col = tex2D(_MainTex, i.uv);
    float dis = distance(_MousePos.xy, i.vertex.xy);
    float4 addColor = _Color;
    addColor.a = saturate(smoothstep(_ColorRadius, 0, dis) - _ColorOffset * sign(col.a-0.1));
    addColor.a =  addColor.a * col.a * _ColorPow;
    return addColor;
}

Border Pass 边框

fixed4 frag (v2f i) : SV_Target
{
    fixed4 col = tex2D(_BorderTex, i.uvBorder);
    float dis = distance(_MousePos.xy, i.vertexBorder.xy);
    col = col + sign(col.a) * smoothstep(_ColorRadius, 0, dis) * _Color * _BorderPow;
    return col;
}

2-2、Shader编写

在Unity中,右键新建一个Shader→Standard Surface Shader
在这里插入图片描述

Shader的代码如下:

Shader "Custom/HoverBtn"
{
    Properties
    {
        _MainTex ("MainTex", 2D) = "white" {}
        _MousePos ("MousePos", Vector) = (0,0,0,0)
        _ColorRedius ("ColorRedius", float) = 40
        [HDR]_Color ("Color", Color) = (0,0,0,1)
        _ColorOffset ("ColorOffset", Range(0, 1)) = 0
        _ColorPow ("ColorPow", Range(0, 1)) = 0.5
        _BorderTex ("BorderTex", 2D) = "white" {}
        _BorderColorMul ("BorderColorPow", float) = 2
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent"}
        Blend SrcAlpha OneMinusSrcAlpha
        // MainTex
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
        // MainColor
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                // 原图形状mask
                float4 vert : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;

                float2 uv : TEXCOORD0;
                float4 vert : TEXCOORD1;
            };


            fixed4 _MousePos;
            float4 _Color;
            float _ColorRedius;
            float _ColorOffset;
            float _ColorPow;

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);

                o.vert = UnityObjectToClipPos(v.vert);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                float dis = distance(_MousePos.xy, i.vertex.xy);
                float4 addColor = _Color;
                addColor.a = saturate(smoothstep(_ColorRedius, 0, dis) - _ColorOffset * sign(col.a-0.1));
                addColor.a =  addColor.a * col.a * _ColorPow;
                return addColor;
            }
            ENDCG
        }
        // Border Color
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertexBorder : POSITION;
                float2 uvBorder : TEXCOORD0;
            };

            struct v2f
            {
                float2 uvBorder : TEXCOORD0;
                float4 vertexBorder : SV_POSITION;
            };

            sampler2D _BorderTex;
            float4 _BorderTex_ST;
            fixed4 _MousePos;
            float _ColorRedius;
            fixed4 _Color;
            float _BorderColorMul;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertexBorder = UnityObjectToClipPos(v.vertexBorder);
                o.uvBorder = TRANSFORM_TEX(v.uvBorder, _BorderTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_BorderTex, i.uvBorder);

                float dis = distance(_MousePos.xy, i.vertexBorder.xy);
                
                col = col + sign(col.a) * smoothstep(_ColorRedius, 0, dis) * _Color * _BorderColorMul;
                return col;
            }
            ENDCG
        }
    }
}

2-3、控制代码编写

新建个脚本,挂载在按钮上,响应鼠标的悬浮和点击事件:

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class HoverBtn : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    [SerializeField, ColorUsage(true, true)]
    private Color color = Color.white;

    [SerializeField]
    private int radius = 100;

    private float scaleFactor;
    private Material material;
    private Canvas canvas;
    Coroutine coroutine_hide;
    Coroutine coroutine_show;

    void Start()
    {
        canvas = GetComponentInParent<Canvas>();
        scaleFactor = canvas.scaleFactor;
        var image = GetComponent<Image>();
        material = new(image.material);
        image.material = material;
        material.SetColor("_Color", color);
        material.SetFloat("_ColorRadius", radius);
        material.SetFloat("_ScaleFactor", scaleFactor);
    }
    private void Update()
    {
        material.SetVector("_MousePos", Input.mousePosition);
        if (scaleFactor != canvas.scaleFactor)
        {
            scaleFactor = canvas.scaleFactor;
            material.SetFloat("_ScaleFactor", scaleFactor);
        }
    }

    void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    {
        if (null != coroutine_show)
        {
            StopCoroutine(coroutine_show);
        }
        if (null != coroutine_hide)
        {
            StopCoroutine(coroutine_hide);
        }
        coroutine_show = StartCoroutine(ChangeInnerColorStateAsync(0.3f, true));
    }

    private IEnumerator ChangeInnerColorStateAsync(float duration, bool show)
    {
        #region Ease
        static float inCubic(float t) => t * t * t;
        static float outCubic(float t) => inCubic(t - 1f) + 1f;
        #endregion

        float time = 0;
        float start = material.GetFloat("_ColorOffset");
        float end = show ? 0 : 0.98f;    // 当 _ColorOffset = 0 显示内部颜色
        Func<float, float> ease = show ? outCubic : inCubic;
        while (time < duration)
        {
            time += Time.deltaTime;
            var p = ease(time / duration);
            material.SetFloat("_ColorOffset", Mathf.Lerp(start, end, p));
            yield return null;
        }
    }

    void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
    {
        if (null != coroutine_hide)
        {
            StopCoroutine(coroutine_hide);
        }
        coroutine_hide = StartCoroutine(HideInnerColorAsync());
    }

    private IEnumerator HideInnerColorAsync()
    {
        //等待ShowInnerColorAsync结束
        if (null != coroutine_show)
        {
            yield return coroutine_show;
        }
        coroutine_hide = StartCoroutine(ChangeInnerColorStateAsync(0.1f, false));
    }
}

2-4、材质球、材质、相机设置、UI设置、Post设置

(1)新建个材质球,使用HoverBtn这个Shader:
在这里插入图片描述
参数设置如下:
在这里插入图片描述
主要注意ColorRedius和Color两个参数。

(2)材质
右键另存为到本地,一个是背景一个是边框
在这里插入图片描述
在这里插入图片描述
(3)UI搭建

一个GridLayoutGroup父节点:
在这里插入图片描述
按钮设置:
在这里插入图片描述
可以修改按钮的颜色:
在这里插入图片描述

(4)Canvas渲染模式
在这里插入图片描述
(5)相机设置
在这里插入图片描述
相机增加Post Volume和Post Layer组件:
在这里插入图片描述
Post插件需要导入,设置Layer层,不做赘述。

整体搭建如下:
在这里插入图片描述

2-5、运行结果

在这里插入图片描述

三、参考链接

1、UI动效 01
2、UGUI 实现超赞 Win10 日历悬停效果

四、后记

总结一下就是:
(1)编写Shader
(2)编写C#脚本响应鼠标事件,传递鼠标位置
(3)其他效果设置
(4)运行起来

如果觉得本篇文章有用别忘了点个关注,关注不迷路,持续分享更多Unity干货文章。


你的点赞就是对博主的支持,有问题记得留言:

博主主页有联系方式。

博主还有跟多宝藏文章等待你的发掘哦:

专栏方向简介
Unity3D开发小游戏小游戏开发教程分享一些使用Unity3D引擎开发的小游戏,分享一些制作小游戏的教程。
Unity3D从入门到进阶入门从自学Unity中获取灵感,总结从零开始学习Unity的路线,有C#和Unity的知识。
Unity3D之UGUIUGUIUnity的UI系统UGUI全解析,从UGUI的基础控件开始讲起,然后将UGUI的原理,UGUI的使用全面教学。
Unity3D之读取数据文件读取使用Unity3D读取txt文档、json文档、xml文档、csv文档、Excel文档。
Unity3D之数据集合数据集合数组集合:数组、List、字典、堆栈、链表等数据集合知识分享。
Unity3D之VR/AR(虚拟仿真)开发虚拟仿真总结博主工作常见的虚拟仿真需求进行案例讲解。
Unity3D之插件插件主要分享在Unity开发中用到的一些插件使用方法,插件介绍等
Unity3D之日常开发日常记录主要是博主日常开发中用到的,用到的方法技巧,开发思路,代码分享等
Unity3D之日常BUG日常记录记录在使用Unity3D编辑器开发项目过程中,遇到的BUG和坑,让后来人可以有些参考。
;