うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】SpriteRenderer を FillAmount する

uGUI の ImageIMeshModifier を利用すれば、頂点座標や uv の変更ができました

しかし、SpriteRenderer では Sprite の頂点座標や uv を利用しているため変更ができません

そのため、Shader を利用して FillAmount を実装する必要があります

使い方

SpriteRenderer がある GameObject に SpriteRendererFillAmount を追加することで利用できます

f:id:hacchi_man:20210123000718p:plain

値の変更は FillAmount に直接値を代入することで変更できます

[SerializeField]
private  SpriteRendererFillAmount _spriteFill;

private void Awake()
{
    _spriteFill.FillAmount = 0.5f;
}

shader の実装

難しい実装をする必要は無く、clip を利用して _FillAmount の値に応じて描画しないように実装するだけです

一応反転して使えるように、_Reverse 時の処理も記述しています

                clip(
                    lerp(
                        -i.uv.y + _FillAmount,
                        i.uv.y - (1 - _FillAmount),
                        _Reverse
                    )
                );

スクリプトの実装

SpriteRenderer は sealed されているため、継承することができないため
パラメータを操作するコンポーネントを作成します

public sealed class SpriteRenderer : Renderer

material の差し替えが必要なため、runtime で Shader を取得してセットしています

マテリアルが通常と異なるため、バッチング対象外となる点は注意が必要です

var shader = Shader.Find("Hidden/SpriteRendererFillAmount");
_material = new Material(shader);
_spriteRenderer.material = _material;

コード

using UnityEngine;
 
[RequireComponent(typeof(SpriteRenderer))]
public class SpriteRendererFillAmount : MonoBehaviour
{
    [SerializeField, HideInInspector]
    private SpriteRenderer _spriteRenderer;
 
    [SerializeField, Range(0f, 1f)]
    private float _fillAmount = 1f;
    public float FillAmount
    {
        get
        {
            return _fillAmount;
        }
        set
        {
            _fillAmount = Mathf.Clamp01(value);
            if (_material != null)
            {
                _material.SetFloat(PropertyId, _fillAmount);
            }
        }
    }
 
    private void OnValidate()
    {
        FillAmount = _fillAmount;
    }
 
    private static readonly int PropertyId = Shader.PropertyToID("_FillAmount");
 
    private Material _material;
 
    private void Reset()
    {
        _spriteRenderer = GetComponent<SpriteRenderer>();
    }
 
    private void Awake()
    {
        var shader = Shader.Find("Hidden/SpriteRendererFillAmount");
        if (shader == null)
        {
            Debug.LogWarning("Shader Not Found");
            return;
        }

        _material = new Material(shader);
        //Material の差し替え
        _spriteRenderer.material = _material;
        FillAmount = _fillAmount;
    }
}

シェーダー

Shader "Hidden/SpriteRendererFillAmount"
{
    Properties
    {
        [NoScaleOffset]
        _MainTex ("Texture", 2D) = "white" {}
        _FillAmount ("FillAmount", Range(0, 1)) = 0
        [MaterialToggle]
        _Reverse ("Reverse", float) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" }
 
        Blend SrcAlpha OneMinusSrcAlpha
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float4 color: COLOR;
                float2 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 color: COLOR;
                float2 uv : TEXCOORD0;
            };
 
            sampler2D _MainTex;
            float _FillAmount;
            float _Reverse;
 
           v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                o.color = v.color;
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * i.color;
                clip(
                    lerp(
                        -i.uv.y + _FillAmount,
                        i.uv.y - (1 - _FillAmount),
                        _Reverse
                    )
                );
                return col;
            }
            ENDCG
        }
    }
}