How to display a part of a sprite which is below specified lines?

I use a Edge collider as ground in a 2D game.
Now I want to have the ground sprite displayed only below it and along the edges of it I want another sprite displayed.

I think I can use the points of Edge collider ( Vector2[ ] ) to repeatedly draw a sprite between every 2 dots.

But how would I cut off part of my ground sprite that is below those lines, which are represented by Vector2[ ] ?

What you’re looking for is an image mask. This is fairly easily doable with the UI system (thanks to the “Mask” component), but as far as I’m aware is not yet possible in the sprite system at large. (It’s on the roadmap for 5.4 in March)

You should be able to hack it together by placing a world space Canvas in the scene. Though, someone else may have a better suggestion.

I will use plane with shader - Vertex Lit i think, but i can be wrong. Plane and gameobject must be in different layer than the others objects. All what plane cover will be not renderer.

Hi,

I didn’t reply earlier, but I made my own Sprite Mask shader that hides everything above or below (depending on settngs) a curve defined by Vector2[ ] and of course I have a script that passes the points from EdgeCollider2D to the material that uses this shader.

Hope it helps :slight_smile:

Here’s an example:

2373924--161356--Curve_example.png

Note: Only works for pre-set length of Vector2[ ], which is defined by constant PointNum in the shader code.

Shader

Shader "Custom/Sprites/SpriteMask_Lines"
{
   Properties
   {
     [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
     _Color ("Tint", Color) = (1,1,1,1)
     [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
     [MaterialToggle] _BelowOrAbove ("Below or Above", Float) = 0
   }

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

     Cull Off
     Lighting Off
     ZWrite Off
     Blend One OneMinusSrcAlpha

     Pass
     {
     CGPROGRAM
       #pragma vertex vert
       #pragma fragment frag
       #pragma multi_compile _ PIXELSNAP_ON
       #include "UnityCG.cginc"
     
       static const int PointNum = 40;

       struct appdata_t
       {
         float4 vertex  : POSITION;
         float4 color  : COLOR;
         float2 texcoord : TEXCOORD0;
       };

       struct v2f
       {
         float4 vertex  : SV_POSITION;
         fixed4 color  : COLOR;
         half2 texcoord  : TEXCOORD0;
       };
     
       fixed4 _Color;

       v2f vert(appdata_t IN)
       {
         v2f OUT;
         OUT.vertex = mul(UNITY_MATRIX_MVP, IN.vertex);
         OUT.texcoord = IN.texcoord;
         OUT.color = IN.color * _Color;
         #ifdef PIXELSNAP_ON
         OUT.vertex = UnityPixelSnap (OUT.vertex);
         #endif

         return OUT;
       }

       sampler2D _MainTex;
       sampler2D _AlphaTex;
       float _AlphaSplitEnabled;

       float2 points[PointNum];
       float sizeX;
       float sizeY;
       float _BelowOrAbove;

       fixed4 SampleSpriteTexture (float2 uv)
       {
         fixed4 color = tex2D (_MainTex, uv);
         if (_AlphaSplitEnabled)
           color.a = tex2D (_AlphaTex, uv).r;

         return color;
       }

       fixed4 frag(v2f IN) : SV_Target
       {
         //return fixed4(points[0].x, points[0].y, 0, 0);

         float x = IN.texcoord.x * sizeX - sizeX/2;
         float y = IN.texcoord.y * sizeY - sizeY/2;

         fixed4 c = SampleSpriteTexture (IN.texcoord) * IN.color;

         bool hidden = false;

         for(int i = 0; i < PointNum - 1; i++)
         {
           //conditions for x-axis range
           bool leftToRight = points[i  ].x < points[i+1].x;
           bool afterFirst  = points[i  ].x <= x;
           bool beforeSecod = points[i+1].x >= x;

           //line equation
           float slope = (points[i+1].y - points.y) / (points[i+1].x - points.x); //m = (y2 - y1) / (x2 - x1)
           float yOffset = points.y - slope * points.x; //y - y1 = m (x - x1) => b = y1 - m * x1

           bool isBelow = y > slope * x + yOffset; // y1 < m * x1 + b

           if( ( (leftToRight && afterFirst && beforeSecod) ||
              !(leftToRight || afterFirst || beforeSecod)  )
            && ((isBelow && _BelowOrAbove == 0) ||
            !(isBelow || _BelowOrAbove == 0)) )
           {
             hidden = true;
           }
         }

         if(hidden) c = fixed4(0,0,0,0);
         else c.rgb *= c.a;

         return c;
       }
     ENDCG
     }
   }
}

Script

using UnityEngine;
using System.Collections;

[RequireComponent (typeof (Renderer))]
[RequireComponent (typeof (EdgeCollider2D))]
public class SpriteMask_EdgeCollider2D : MonoBehaviour {

   Renderer rend;
   Material mat;
   EdgeCollider2D edgeCollider;

   float sizeXSaved = -1;
   float sizeYSaved = -1;
   Vector2[] pointsSaved;

   void Start()
   {
     rend = GetComponent<Renderer>();
     if(Application.isPlaying) mat = rend.materials[0];
     else mat = rend.sharedMaterials[0];
     edgeCollider = GetComponent<EdgeCollider2D>();
   }
  
   public void Update()
   {
     //if(rend == null) rend = GetComponent<Renderer>();
     //if(mat == null) mat = rend.sharedMaterials[0];
     //if(edgeCollider == null) edgeCollider = GetComponent<EdgeCollider2D>();

     float sizeX = rend.bounds.size.x / transform.localScale.x;
     float sizeY = rend.bounds.size.y / transform.localScale.y;
     if(sizeX != sizeXSaved)
     {
       sizeXSaved = sizeX;
       mat.SetFloat("sizeX", sizeX);
     }
     if(sizeY != sizeYSaved)
     {
       sizeYSaved = sizeY;
       mat.SetFloat("sizeY", sizeY);
     }

     if(pointsSaved == null || pointsSaved.Length != edgeCollider.points.Length)
     {
       pointsSaved = new Vector2[edgeCollider.points.Length];
       for(int i = 0; i < edgeCollider.points.Length; i++) // edgeCollider.points.Length = 1 + surve points (of Bezier Collider)
       {
         pointsSaved[i] = edgeCollider.points[i];
         mat.SetVector("points" + i, edgeCollider.points[i]);
       }
     }
     else for(int i = 0; i < edgeCollider.points.Length; i++) // edgeCollider.points.Length = 1 + surve points (of Bezier Collider)
     {
       if(pointsSaved[i] != edgeCollider.points[i])
       {
         pointsSaved[i] = edgeCollider.points[i];
         mat.SetVector("points" + i, edgeCollider.points[i]);
       }
     }
   }
}
1 Like