0
\$\begingroup\$

In Unity, working on a 2D game, I have a platform game where the platforms can "sink" when jumped on. The platforms are 32x32 sprites, and when sinking, the desired effect is to push the sprite down and avoid drawing the portion of the sprite outside the original 32x32 box:

alt text

I currently do this by placing a sprite mask below the platform. It works very well, but the problem begins when I have several such platforms directly below one another:

enter image description here

I can't get this effect using mask since the mask will also partially cover the upper side of the platform below. An ad-hoc solution is assigning the sprites to different sorting layers, but I sometimes have many such platforms, and they're all conceptually the same, so I'm looking for a cleaner solution.

TL;DR: I want to define a "bounding box" for a specific sprite such that parts of the sprite outside the box will not be drawn.

Note that this is a 2D problem concerning sprites; I've seen solution based on 3D components which are not relevant here.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I prefer to use a simple shader to handle this:

Set a parameter MinY, Any fragment with uv.y less than this value will be discarded.

Shader "Custom/NewShader"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MinY("MinY", Range (0, 1)) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        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;
            float _MinY;

            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
            {
                float2 uv = i.uv;
                if (uv.y<_MinY){
                    discard;
                }

                fixed4 col = tex2D(_MainTex, uv);
                if(col.a==0){
                    discard;
                }
                return col;
            }
            ENDCG
        }
    }
}

And a c# script to control the material parameters:

Cache the original position of the sprite, compare the position every frame and calculate the value of MinY.

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    private Vector3 pos;
    private Material material;
    private float sizeY;
    void Start()
    {
        pos = transform.position;
        var spriteRenderer = GetComponent<SpriteRenderer>();
        material = spriteRenderer.material;
        sizeY = spriteRenderer.size.y;
    }

    void Update()
    {
        var offsetY = (pos - transform.position).y;
        material.SetFloat("_MinY", offsetY / sizeY);

    }
}

This is a rough prototype, there is a lot of room for optimization, maybe you can think of some strategies to reduce computation. :)

Result:

enter image description here

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .