Search Unity

Tutorial: Scratch Card or Scratch-and-win effect

Discussion in 'Community Learning & Teaching' started by amiraniotm, Jan 9, 2021.

  1. amiraniotm

    amiraniotm

    Joined:
    Jun 5, 2018
    Posts:
    2
    Hi everyone!! This is my first post here and I decided to make it because I really had a hard time working around this effect. The only tutorial I could find online used a really sloppy method that did not work for fast pointer movement and I wanted my effect to look clean under any circumstance. The rest of the scratch effects around were paid, and since my project was only for fun I did not want to pay for any assets.

    So here it is! I will take you through how I achieved a scratch-and-win effect as the one in this gif



    I’m starting with a clean project in Unity 2019.

    First I add an Image to my scene



    It is important you set your canvas to World Space, as we will be using two cameras



    I’m adding the Main Camera as the Event Camera to get rid of the warning, but it did not make any other difference for me.

    Now we add a second Camera, which I called “ImageCamera”, and we move the Image to this camera. I created this camera by duplicating the Main Camera (remember to set your ImageCamera tag to “untagged” if you do this)





    Remove the AudioListener component from this camera or it will print a warning constantly at runtime

    Next we add a Sprite and set it to the main camera. This one will be our “scratchable” surface. Add a Box Collider 2D component to this Sprite so it detects click/touch



    I added this generic gray noise image to use as a scratchable cover. You need to add an image to your sprite and resize to fit your camera space. You should see the cover appearing over the image, if it doesn’t show turn off and on your main camera

    Now add a new script to your scratchable sprite. I named it ScratchEffect.



    Lastly, a key piece of this effect. I’m using this amazing tool I found and this wouldn’t have been possible without it. It is a shader that makes everything you cover with it see-through. EVERYTHING. This is why I’m using two cameras. If I just covered the image directly, it would also be rendered transparent when scratched.

    Download the shader from https://github.com/doomlaser/DepthMask-Unity-Shader and add it to a new “shaders” folder under your Assets folder. Hats off to the creator of this amazing tool.



    Next create a New material by going to your assets folder (inside the Editor) and doing Right click->Create->Material. Name it whatever you want, then select it and assign DepthMask for the shader



    We are all set up with the Unity Editor for now. Next, onto the code. I will attach my ScratchEffect script, but here is an explanation of what it does

    First Properties

    Code (CSharp):
    1. public List<LineRenderer> lineRenderers = new List<LineRenderer>();
    2.     public List<List<Vector3>> allLinePoints = new List<List<Vector3>>();
    3.     public Material lineMaterial;
    4.    
    5.     private LineRenderer currentLineRenderer;
    6.     private List<Vector3> currentLinePoints = new List<Vector3>();
    7.     private int lineCount = 0;
    • A list of LineRenderers. These will be used for storing all the LineRenderers. This are the components used to draw the lines (read the docs https://docs.unity3d.com/ScriptReference/LineRenderer.html) I will explain later on why I use multiple instead of just one

    • A List of Lists of Vector3. Each LineRenderer needs a List of Vector3 for storing line points. Since we are using multiple LineRenderers, we need multiple Lists of Vector3

    • Material, which will be the material created from the shader we downloaded. DON’T FORGET TO ADD IT TO YOUR OBJECT IN THE EDITOR

    • currentLineRenderer stores the LineRenderer currently in use

    • currentLinePoints stores the points from the current LineRenderer

    • lineCount stores how many lines are in-screen, to retrieve current one from list
    Now Functions

    Code (CSharp):
    1.     public void NewLine()
    2.     {
    3.         GameObject newGameObject = new GameObject();
    4.         LineRenderer newLineRenderer;
    5.  
    6.         newLineRenderer = newGameObject.AddComponent<LineRenderer>();
    7.         newLineRenderer.positionCount = 0;
    8.         newLineRenderer.startColor = Color.white;
    9.         newLineRenderer.endColor = Color.white;
    10.         newLineRenderer.startWidth = 0.4f;
    11.         newLineRenderer.endWidth = 0.4f;
    12.         newLineRenderer.useWorldSpace = true;
    13.         newLineRenderer.material = lineMaterial;
    14.  
    15.         lineRenderers.Add(newLineRenderer);
    16.        
    17.         List<Vector3> linePoints = new List<Vector3>();
    18.        
    19.         allLinePoints.Add(linePoints);
    20.     }
    This function initializes a new empty GameObject and attaches a LineRenderer to it. Then we set the properties for this lineRenderer (you can play with the line width or whatever property you want, but the material is what makes it so it scratches out the surface)

    Finally we store the LineRenderer in our list and we create a List of Vector3 for its points, and store that inside our Lists List.

    Then, the core of the effect

    Code (CSharp):
    1.     public void OnMouseOver()
    2.     {
    3.         if(Input.GetMouseButtonDown(0))
    4.         {
    5.             lineCount += 1;
    6.             NewLine();
    7.             currentLinePoints = allLinePoints[lineCount - 1];
    8.             currentLineRenderer = lineRenderers[lineCount - 1];
    9.         }
    10.         if(Input.GetMouseButton(0))
    11.         {
    12.             Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    13.  
    14.             worldPos.z = -8;
    15.  
    16.             if(currentLinePoints.Contains(worldPos) == false)
    17.             {
    18.                 currentLinePoints.Add(worldPos);
    19.  
    20.                 currentLineRenderer.positionCount = currentLinePoints.Count;
    21.                 currentLineRenderer.SetPosition(currentLineRenderer.positionCount - 1, worldPos);
    22.             }
    23.         }
    24.     }
    I’m using OnMouseOver so we only draw lines when the mouse is over our sprite. Then GetMouseButtonDown adds one to our line counter, sets a new LineRenderer and assigns the last list element as the current LineRenderer and its points.

    Where I set worldPos.z = -8, remember to set it so it appears under your MainCamera. In this case, the z component of my MainCamera is -10, so -8 would be under it.

    Now, why multiple LineRenderers? Simply put, the reason is one LineRenderer can only draw one Line. If we were to use only one LineRenderer, each time we released our click/touch and clicked/touched again, a joining line would be drawn from the last point you draw to the start of the new line. This is a workaround to that.

    Lastly, I use GetMouseButton to follow our pointer position and add points to our LineRenderer so it draws a line. And voilá! It should be working now.



    Hope you find it helpful, you can reach out with any doubts!
     

    Attached Files:

    game4444 and umair21 like this.
  2. umair21

    umair21

    Joined:
    Mar 4, 2016
    Posts:
    147
    Hi, thanks for this awesome explained tutorial. Just two questions

    1. Can we get scratch progress? Like how much did we scratch already?
    2. Can we change shape of the mask or scratching shape?
     
  3. game4444

    game4444

    Joined:
    Feb 7, 2017
    Posts:
    5
    Is there anyway to calculate the progress of scratch?
     
  4. amiraniotm

    amiraniotm

    Joined:
    Jun 5, 2018
    Posts:
    2
    Sorry friend, I got out of videogames for a while and didn't pay attention to this post. I'll answer your questions anyway:

    1. Progress, you can only approximate it, but it is really tricky. Unfortunately I've lost the code by now, but you can calculate the length of the lines, multiply by the width, and the difference with the total "scratching area" will give you the scratching progress. Notice that this doesn't take in account overlapping lines, so you would have to adjust for this!

    2. Not that I'm aware of!

    Sorry again for the VERY delayed response. A lot of things happened!
     
  5. NovakN

    NovakN

    Joined:
    Jun 22, 2022
    Posts:
    1
    This works really well for us, thank you! Facing same issue here. Help is appreciated.
     
    amiraniotm likes this.