Unity 2d Sprite 环形进度条


GIF 2025-12-25 11-06-21.gif




Shader


Shader "IceGlacier/URP/SpriteRadial_Fixed"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
        _Progress ("Progress", Range(0.0, 1.0)) = 1.0
        _Smoothness ("Anti-Aliasing", Range(0.0, 0.1)) = 0.01
    }

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

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            Name "SpriteRadialFixed"
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            // Macro is safer than static const for older compilers
            #define INV_TWO_PI 0.1591549

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

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

            CBUFFER_START(UnityPerMaterial)
                float4 _Color;
                float _Progress;
                float _Smoothness;
            CBUFFER_END

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = input.uv;
                output.color = input.color * _Color;
                return output;
            }

            float4 frag(Varyings input) : SV_Target
            {
                float4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                col *= input.color;

                // Explicit float2 construction to avoid compiler ambiguity
                float2 uv = input.uv - float2(0.5, 0.5);

                // Standard Atan2 (Clockwise start from 12 o'clock)
                // atan2(x,y) returns angle from Y axis.
                // We multiply by 1/(2PI) to map to -0.5 ~ 0.5
                // frac() handles the negative wrap-around automatically, resulting in 0.0 ~ 1.0
                float angle = frac(atan2(uv.x, uv.y) * INV_TWO_PI);

                // Branchless Mask Logic
                // If angle > Progress, alpha becomes 0
                // smoothstep handles the AA edge
                float mask = smoothstep(angle - _Smoothness, angle + _Smoothness, _Progress);

                col.a *= mask;

                return col;
            }
            ENDHLSL
        }
    }
}


控制脚本


using UnityEngine;

namespace GameScripts
{
    /// <summary>
    /// Sprite 径向进度条控制器
    /// 特性:支持 SRP Batcher,无材质实例化开销,Editor 实时预览
    /// </summary>
    [ExecuteAlways]
    public class TaskBar : MonoBehaviour
    {

        // Shader 属性 ID 缓存 (Static Readonly 避免每帧 Hash 计算)
        private static readonly int ProgressID = Shader.PropertyToID( "_Progress" );
        private static readonly int SmoothnessID = Shader.PropertyToID( "_Smoothness" );
        private static readonly int ColorID = Shader.PropertyToID( "_Color" );

        [Header( "Settings" )]
        [SerializeField, Range( 0f, 1f )] private float progress = 1.0f;
        [SerializeField, Range( 0f, 0.1f )] private float smoothness = 0.01f;
        [SerializeField] private Color shaderTint = Color.white;

        // 运行时缓存
        [SerializeField] private SpriteRenderer _renderer;
        private MaterialPropertyBlock _mpb;
        private bool _isDirty = true;

        private void OnEnable( )
        {
            Initialize( );
            _isDirty = true; // 启用时强制刷新一次
        }

        private void Initialize( )
        {
            if ( _renderer == null ) _renderer = GetComponent<SpriteRenderer>( );
            if ( _mpb == null ) _mpb = new MaterialPropertyBlock( );
        }

#if UNITY_EDITOR
        private void OnValidate( )
        {
            // 编辑器下属性面板修改时立即刷新
            _isDirty = true;
            // 在编辑器非运行模式下,Update 不会每帧跑,手动触发
            if ( !Application.isPlaying )
            {
                LateUpdate( );
            }
        }
#endif

        private void LateUpdate( )
        {
            if ( _isDirty )
            {
                UpdateVisuals( );
                _isDirty = false;
            }
        }

        /// <summary>
        /// 提交数据到 GPU
        /// </summary>
        private void UpdateVisuals( )
        {
            if ( _renderer == null ) Initialize( );

#if UNITY_EDITOR
            if ( _mpb == null ) return;
#endif

            // 必须先 Get,以防覆盖了该 Renderer 上其他的 MPB 属性
            _renderer.GetPropertyBlock( _mpb );

            _mpb.SetFloat( ProgressID, progress );
            _mpb.SetFloat( SmoothnessID, smoothness );
            _mpb.SetColor( ColorID, shaderTint );

            _renderer.SetPropertyBlock( _mpb );
        }

        // --- Public API ---

        /// <summary>
        /// 设置进度 (0 ~ 1)
        /// </summary>
        public void SetProgress( float value )
        {
            float clamped = Mathf.Clamp01( value );
            if ( !Mathf.Approximately( progress, clamped ) )
            {
                progress = clamped;
                _isDirty = true;
            }
        }

        /// <summary>
        /// 设置抗锯齿程度 (0 ~ 0.1)
        /// </summary>
        public void SetSmoothness( float value )
        {
            float clamped = Mathf.Clamp( value, 0f, 0.1f );
            if ( !Mathf.Approximately( smoothness, clamped ) )
            {
                smoothness = clamped;
                _isDirty = true;
            }
        }

        /// <summary>
        /// 设置 Shader 叠加色
        /// </summary>
        public void SetTint( Color color )
        {
            if ( shaderTint != color )
            {
                shaderTint = color;
                _isDirty = true;
            }
        }
    }
}



Unity 2d Sprite血条shader

2d被击效果 shader

评 论