2d被击效果 shader



GIF 2025-12-25 10-35-39.gif

Shader "Game/2D/TreeHitRing_Unlit"
{
    Properties
    {
        [PerRendererData]_MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)

        _HitStrength ("Hit Strength", Range(0,1)) = 0
        _HitPos ("Hit Pos (UV)", Vector) = (0.5,0.5,0,0)
        _HitT ("Hit T (0..1)", Range(0,1)) = 0
        _RingMaxRadius ("Ring Max Radius", Range(0,1)) = 0.55
        _RingWidth ("Ring Width", Range(0.001,0.3)) = 0.06
        _Flash ("Flash", Range(0,1)) = 0.25
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "RenderType"="Transparent"
            "IgnoreProjector"="True"
            "RenderPipeline"="UniversalPipeline"
        }

        Cull Off
        ZWrite Off
        // Blend One OneMinusSrcAlpha
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv         : TEXCOORD0;
                float4 color      : COLOR;
            };

            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv          : TEXCOORD0;
                float4 color       : COLOR;
            };

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            float4 _MainTex_ST;
            float4 _Color;

            float _HitStrength;
            float4 _HitPos;
            float _HitT;
            float _RingMaxRadius;
            float _RingWidth;
            float _Flash;

            Varyings vert (Attributes v)
            {
                Varyings o;
                o.positionHCS = TransformObjectToHClip(v.positionOS.xyz);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.color = v.color * _Color;
                return o;
            }

            half4 frag (Varyings i) : SV_Target
            {
                half4 baseCol = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv) * i.color;

                // 透明像素直接返回,避免边缘发光
                if (baseCol.a <= 0.001) return baseCol;

                float2 p = i.uv - _HitPos.xy;
                float dist = length(p);

                // 扩散环半径:0 -> _RingMaxRadius
                float r = saturate(_HitT) * _RingMaxRadius;

                // 环:基于 abs(dist - r) 的高斯/软边
                float x = (dist - r) / max(_RingWidth, 1e-5);
                float ring = exp(-x * x);          // 1 在环上,离开快速衰减

                // 闪白:前段更强,后段衰减
                float flash = (1.0 - saturate(_HitT)) * _Flash;

                float hit = _HitStrength * (ring * 0.85 + flash);
                hit *= baseCol.a;

                // 轻微偏白(你也可以改成偏黄/偏绿)
                baseCol.rgb = saturate(baseCol.rgb + hit);

                return baseCol;
            }
            ENDHLSL
        }
    }
}


控制脚本

using System.Collections;
using UnityEngine;

[DisallowMultipleComponent]
public class TreeHitFx : MonoBehaviour
{
    static readonly int ID_HitStrength = Shader.PropertyToID( "_HitStrength" );
    static readonly int ID_HitPos = Shader.PropertyToID( "_HitPos" );
    static readonly int ID_HitT = Shader.PropertyToID( "_HitT" );

    [Header( "Timing" )]
    [SerializeField] float duration = 0.18f;

    [Header( "Strength" )]
    [Range( 0f, 1f )]
    [SerializeField] float strength = 1.0f;

    SpriteRenderer sr;
    MaterialPropertyBlock mpb;
    Coroutine co;

    public void Init(  SpriteRenderer icon )
    {
        sr = icon;
        mpb = new MaterialPropertyBlock( );
        // 初始化确保没残留
        sr.GetPropertyBlock( mpb );
        mpb.SetFloat( ID_HitStrength, 0f );
        mpb.SetFloat( ID_HitT, 0f );
        sr.SetPropertyBlock( mpb );
    }

    /// <summary>
    /// worldHitPos:砍中点(世界坐标)
    /// </summary>
    public void PlayHit( Vector2 worldHitPos )
    {
        if ( !sr || !sr.sprite ) return;

        // 计算命中点 UV(基于 sprite bounds 的近似;对大多数“树”足够用)
        Vector2 uv = WorldToSpriteUV( worldHitPos );

        sr.GetPropertyBlock( mpb );
        mpb.SetVector( ID_HitPos, new Vector4( uv.x, uv.y, 0f, 0f ) );
        mpb.SetFloat( ID_HitStrength, strength );
        sr.SetPropertyBlock( mpb );

        if ( co != null ) StopCoroutine( co );
        co = StartCoroutine( CoHitAnim( ) );
    }

    IEnumerator CoHitAnim( )
    {
        float t = 0f;
        while ( t < duration )
        {
            t += Time.deltaTime;
            float k = Mathf.Clamp01( t / duration );

            // 你可以换成 AnimationCurve,这里先给一个“先快后慢”的手感
            float hitT = 1f - Mathf.Pow( 1f - k, 2f );

            sr.GetPropertyBlock( mpb );
            mpb.SetFloat( ID_HitT, hitT );
            sr.SetPropertyBlock( mpb );

            yield return null;
        }

        // 收尾清零,避免停留在某个状态
        sr.GetPropertyBlock( mpb );
        mpb.SetFloat( ID_HitStrength, 0f );
        mpb.SetFloat( ID_HitT, 0f );
        sr.SetPropertyBlock( mpb );

        co = null;
    }

    Vector2 WorldToSpriteUV( Vector2 worldPos )
    {
        // 转本地
        Vector2 local = transform.InverseTransformPoint( worldPos );

        // sprite.bounds 是本地单位下的 AABB(包含 pivot 影响)
        Bounds b = sr.sprite.bounds;
        float u = Mathf.InverseLerp( b.min.x, b.max.x, local.x );
        float v = Mathf.InverseLerp( b.min.y, b.max.y, local.y );
        return new Vector2( u, v );
    }
}




Unity 2d Sprite 环形进度条

Unity Spine AnimationReferenceAsset 使用笔记

评 论