Make a Scratch Card Effect in Unity

Scratch card Effect In Unity

Many games use a scratch card to reward the user. You can make the scratch card effect using counting the pixels of the texture and set the alpha color of the pixel to 0, whenever the user touches some area in the card. But Setpixel() Method of texture has slow and it gives a jerky effect to our card effect. To overcome this issue, we are going to make a scratch card effect using the shader.

Follow the instructions step by step to make a scratch card effect. You can download a unitypackage from the link given at any time.

Step 1: Create a Mask Shader

This shader gives a scratch card effect on any UI Image.

Shader "Unity3dTuts/TextureMasking" {
    Properties {
        _MainTex ("Main", 2D) = "white" {}
        _MaskTex ("Mask", 2D) = "white" {}
    }

    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        ZWrite Off
        ZTest Off
        Blend SrcAlpha OneMinusSrcAlpha
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma fragmentoption ARB_precision_hint_fastest
            #include "UnityCG.cginc"

            uniform sampler2D _MainTex;
            uniform sampler2D _MaskTex;
            uniform float4 _MainTex_ST;
            uniform float4 _MaskTex_ST;

            struct app2vert
            {
                float4 position: POSITION;
                float2 texcoord: TEXCOORD0;
            };

            struct vert2frag
            {
                float4 position: POSITION;
                float2 texcoord: TEXCOORD0;
            };

            vert2frag vert(app2vert input)
            {
                vert2frag output;
                output.position = UnityObjectToClipPos(input.position);
                output.texcoord = TRANSFORM_TEX(input.texcoord, _MainTex);
                return output;
            }

            fixed4 frag(vert2frag input) : COLOR
            {
                fixed4 main_color = tex2D(_MainTex, input.texcoord);
                fixed4 mask_color = tex2D(_MaskTex, input.texcoord);
                return fixed4(main_color.r, main_color.g, main_color.b, main_color.a * (1.0f - mask_color.a));
            }
            ENDCG
        }
    }
}

How to use this Shader?

  • Create a material and select Unity3dTuts/TextureMasking as shader
  • Create a UI Image and apply this material to it.
  • In this material, Set the ScratchCard image as the main texture.

Step 2: Create a mask Eraser Shader

This shader is used to erase the scratch card effect from any image.

Shader "Unity3dTuts/MaskEraser" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
    }

    SubShader {
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        ZWrite Off
        ZTest Off
        Blend One One
        BlendOp Max
        Pass {
            Lighting Off
            SetTexture[_MainTex] {
                //matrix [_UVMatrix]
                ConstantColor [_Color]
                combine texture * constant
            }
        }
    }
}

How to use this Shader?

  • Create a new Material and select Unity3dTuts/MaskEraser as shader
  • In this material, set your brush image as the main texture.

Step 3: Create Camera Post Effect Script

This script basically invokes the event whenever OnPostRender() methods are called in the maskCamera(see Step 4).

using UnityEngine;

public class CameraPostEffect : MonoBehaviour
{
    public delegate void postEffect();
    public event postEffect OnPostEffect;

    private void OnPostRender()
    {
        if (OnPostEffect != null)
        {
            OnPostEffect.Invoke();
        }
    }

}

Step 4: Mask Camera

You need to add another camera called Mask Camera to show the scratch effect on the screen.

How to Make Mask Camera?

  • In Hierarchy panel create a new camera
  • In this Camera component as Clear Flag Select ”Don’t clear”
  • Set Projection to “Orthographic” and its size to 5.
  • Attached a Camera Post Effect script.

Step 5: Create a Scratch Card Effect Script

  • Use RenderTeture to make our card visible upon the image using the maskCamera and the shader.
  • Only perform the erase effect if the user touches
  • We are using GL library to render in target render texture of the camera to generate the effect.
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class ScratchCardEffect : MonoBehaviour
{
    public Camera maskCamera;
    public GameObject maskObject;
    public Material EraserMaterial;
    public Vector2 erasorSize;

    private Texture2D tex;
    private Rect ScreenRect;
    private RenderTexture rt;
    private bool firstFrame;
    private Vector2? newHolePosition;
    private RectTransform rectTransform;
    private Camera mainCamera;

    private void EraseBrush(Vector2 imageSize, Vector2 imageLocalPosition)
    {
        Rect textureRect = new Rect(0.0f, 0.0f, 1.0f, 1.0f); //this will get erase material texture part
        Rect positionRect = new Rect(
            (imageLocalPosition.x - 0.5f * EraserMaterial.mainTexture.width) / imageSize.x,
            (imageLocalPosition.y - 0.5f * EraserMaterial.mainTexture.height) / imageSize.y,
            EraserMaterial.mainTexture.width / imageSize.x,
            EraserMaterial.mainTexture.height / imageSize.y
        ); //This will Generate position of eraser according to mouse position and size of eraser texture

        //Draw Graphics Quad using GL library to render in target render texture of camera to generate effect
        GL.PushMatrix();
        GL.LoadOrtho();
        for (int i = 0; i < EraserMaterial.passCount; i++)
        {
            EraserMaterial.SetPass(i);
            GL.Begin(GL.QUADS);
            GL.Color(Color.white);
            GL.TexCoord2(textureRect.xMin, textureRect.yMax);
            GL.Vertex3(positionRect.xMin, positionRect.yMax, 0.0f);
            GL.TexCoord2(textureRect.xMax, textureRect.yMax);
            GL.Vertex3(positionRect.xMax, positionRect.yMax, 0.0f);
            GL.TexCoord2(textureRect.xMax, textureRect.yMin);
            GL.Vertex3(positionRect.xMax, positionRect.yMin, 0.0f);
            GL.TexCoord2(textureRect.xMin, textureRect.yMin);
            GL.Vertex3(positionRect.xMin, positionRect.yMin, 0.0f);
            GL.End();
        }
        GL.PopMatrix();
    }

    private void OnEnable()
    {
        maskCamera.GetComponent<CameraPostEffect>().OnPostEffect += OnPost;
        StartCoroutine(StartScratch());
    }

    private void OnDisable()
    {
        maskCamera.GetComponent<CameraPostEffect>().OnPostEffect -= OnPost;
        maskCamera.targetTexture = null;
        maskObject.GetComponent<Image>().material.SetTexture("_MaskTex", null);
    }

    public IEnumerator StartScratch()
    {
        firstFrame = true;
        //Get Erase effect boundary area
        rectTransform = maskObject.GetComponent<RectTransform>();
        ScreenRect = rectTransform.rect;
        mainCamera = Camera.main;

        //Create new render texture for camera target texture
        var renderTextureDesc = new RenderTextureDescriptor(Screen.width, Screen.height, RenderTextureFormat.ARGB32, 0);
        rt = new RenderTexture(renderTextureDesc);
        yield return rt.Create();

        Graphics.Blit(tex, rt, maskObject.GetComponent<Image>().material);
        maskCamera.targetTexture = rt;

        yield return null;

        //Set Mask Texture to dust material to Generate Dust erase effect
        maskObject.GetComponent<Image>().material.SetTexture("_MaskTex", rt);
    }

    public void Update()
    {
        newHolePosition = null;

        if (Input.GetMouseButton(0)) //Check if MouseDown
        {
            Vector2 v = Vector2.zero;
            Rect worldRect = ScreenRect;

            if (RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, Input.mousePosition, mainCamera, out Vector2 localPos))
            {
                v = localPos;
            }

            if (worldRect.Contains(v))
            {
                newHolePosition = new Vector2(erasorSize.x * (v.x - worldRect.xMin) / worldRect.width, erasorSize.y * (v.y - worldRect.yMin) / worldRect.height);
            }
        }
    }

    public void OnPost()
    {
        if (firstFrame)
        {
            firstFrame = false;
            GL.Clear(false, true, new Color(0.0f, 0.0f, 0.0f, 0.0f));
        }

        //Generate GL quad according to eraser material texture
        if (newHolePosition != null)
        {
            EraseBrush(new Vector2(erasorSize.x, erasorSize.y), newHolePosition.Value);
        }
    }
}

You can download the UnityPackage of the source code from here.

2 thoughts on “Make a Scratch Card Effect in Unity”

  1. How to detect Scratch Card is fully Scratched ? Is there Any Way?

    Please let me Know.
    Thank You.

Leave a Reply