Skip to content

Create a Jigsaw Puzzle Game in Unity

Jigsaw Puzzle Game

Section 4 – Create a Jigsaw Puzzle Game in Unity

Download the assets needed for this tutorial from https://faramira.com/downloads/jigsaw/assets_to_dowmload.zip.

You can find the entire source code of this project in the GitHub repo. https://github.com/shamim-akhtar/jigsaw-puzzle/tree/main

View the Tutorial on YouTube

This tutorial is divided into four broad sections, with each containing one or more subsections. 

You are now reading the fourth section, Section 4 – Create a Jigsaw Puzzle Game in Unity

THIS SECTION IS UNDER CONSTRUCTION NOW

In this fourth section, we will create the final product, the Jigsaw puzzle game. In the previous section, we learned how to create an entire jigsaw board from an image. In this section, we will first apply tile movement by clicking, holding, and dragging the tile. We will then implement a custom depth sorting so that the clicked tiles are in focus. Finally, we will implement the User Interface and the game logic of a Jigsaw puzzle game.

The Scene

Right-click on the project window and create a new scene.

Name it Scene_JigsawGame. This scene will be our test bed for the various features we will implement in the following sections. Add an empty GameObject to the scene and name it JigsawBoard. Select the JigsawBoard game object, go to the inspector, and add the script component BoardGen that we created in the last section. We will continue working on the same script.

Now add a new game object in JigsawBoard and name it Tiles. Drag and drop this into the field Parents For Tiles. Set the image filename to images/jigsaws/flower_12_8.

Click Play to see if everything is working as expected. At this point, it should behave the same as we implemented in our previous section.

Tile Movement Script

TileMovement, as the name suggests, is a script that allows for the handling of tile selection and movement by holding and dragging the left mouse button.

Go ahead and create a new C# script and name it TileMovement. Double-click and open the file in Visual Studio or your favourite editor.

This class will handle the movement behaviour for each tile in our jigsaw puzzle. We start by declaring a public property called tile. This property represents the specific tile that this script will control. We then define a private variable called mOffset. This variable will store the offset between the mouse position and the position of the tile when the mouse button is pressed down. We will use this offset to ensure smooth dragging behaviour.

public class TileMovement : MonoBehaviour
{
  public Tile tile { get; set; }
  private Vector3 mOffset = new Vector3(0.0f, 0.0f, 0.0f);

After that, we define a private method called GetCorrectPosition. This method will calculate and return the correct position for the tile based on its xIndex and yIndex. We will use this method later to snap the tile to its proper position.

  private Vector3 GetCorrectPosition()
  {
    return new Vector3(tile.xIndex * 100f, tile.yIndex * 100f, 0f);
  }Code language: PHP (php)

Now, let’s proceed with the next part of the code by adding the OnMouseDown method. This method is called automatically when the mouse button is pressed down while the cursor is over the GameObject to which this script is attached. In this method, we will set the offset between the mouse position and the tile’s position.

  private void OnMouseDown()
  {
    if (!GameApp.Instance.TileMovementEnabled) return;
    if(EventSystem.current.IsPointerOverGameObject())
    {
      return;
    }
    mOffset = transform.position - Camera.main.ScreenToWorldPoint(
      new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.0f));
  }Code language: C# (cs)

Then, we implement the OnMouseDrag method. This method is called automatically when the mouse is dragged while the cursor is over the GameObject to which this script is attached. Inside this method, we’ll handle updating the position of the tile as it’s being dragged.

  private void OnMouseDrag()
  {
    if (!GameApp.Instance.TileMovementEnabled) return;
    if (EventSystem.current.IsPointerOverGameObject())
    {
      return;
    }
    Vector3 curScreenPoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.0f);
    Vector3 curPosition = Camera.main.ScreenToWorldPoint(curScreenPoint) + mOffset;
    transform.position = curPosition;
  }Code language: C# (cs)

After that, we implement the OnMouseUp method, which is called automatically when the mouse button is released after being pressed down while the cursor is over the GameObject that this script is attached to. Inside this method, we will handle snapping the tile to its correct position if it’s close enough to it; this distance is set to 20 units.

  private void OnMouseUp()
  {
    if (!GameApp.Instance.TileMovementEnabled) return;
    if (EventSystem.current.IsPointerOverGameObject())
    {
      return;
    }
    float dist = (transform.position - GetCorrectPosition()).magnitude;
    if(dist < 20.0f)
    {
      transform.position = GetCorrectPosition();
      onTileInPlace?.Invoke(this);
    }
  }Code language: C# (cs)

And that’s it! We’ve implemented the TileMovement class, which allows us to handle mouse input for dragging and snapping tiles in our jigsaw puzzle game. 

We will now set this script component to each tile. To do so, open up the BoardGen script, go to the CreateGameObjectFromTile function, and add the TileMovement component to the tile game object as indicated by the highlighted section below.

  public static GameObject CreateGameObjectFromTile(Tile tile)
  {
    GameObject obj = new GameObject();

    obj.name = "TileGameObe_" + 
      tile.xIndex.ToString() + 
      "_" + 
      tile.yIndex.ToString();

    obj.transform.position = new Vector3(
      tile.xIndex * Tile.tileSize, 
      tile.yIndex * Tile.tileSize, 
      0.0f);

    SpriteRenderer spriteRenderer = obj.AddComponent<SpriteRenderer>();
    spriteRenderer.sprite = SpriteUtils.CreateSpriteFromTexture2D(
      tile.finalCut,
      0,
      0,
      Tile.padding * 2 + Tile.tileSize,
      Tile.padding * 2 + Tile.tileSize);

    BoxCollider2D box = obj.AddComponent<BoxCollider2D>();

    TileMovement tileMovement = obj.AddComponent<TileMovement>();
    tileMovement.tile = tile;

    return obj;
  }
Code language: C# (cs)

Go to the Unity editor. Right-click on the project window and add an event system. Now click play. Once the tiles are generated, you can click on any tile, drag it to different locations, and release it anywhere you release your mouse. Go ahead and try it out.

Sorting of Tiles

In our jigsaw board, all the tiles can be in the same layer and in the same depth order. However, based on which tile we pick, we want to bring it up in the render order to draw it on top of all other tiles and set the correct z value so that raycast can also pick it up even if it overlaps with different tiles.

As such, we will need to sort the tiles based on depth values. This is to facilitate the ordering of tiles when we click and select them. Sorting Layer and Render Order only allow the order of rendering. That is not sufficient for us as we also want to choose the tile on top and not one hidden below.

To obtain a proper z sorting so that our Raycast works, we will need to set the z value of the tiles at runtime and change the value when we select a tile.Go ahead and add a new C# file and name it TilesSorting. Double-click and open the file in Visual Studio or your favourite editor.

First, we remove MonoBehavior (as we do not want this class to derive from Monobehavior), and then we remove the Start and Update methods. 

Now, let’s add a private mSortIndices of type List of SpriteRenderer. This list will keep track of all the sprite renderers we want to sort. Next, we create a constructor method, which we keep empty for now.

public class TilesSorting
{
  private List<SpriteRenderer> mSortIndices = new List<SpriteRenderer>();
  public TilesSorting()
  {
  }Code language: C# (cs)

We then define a public method called Clear. This method clears the mSortIndices list, effectively resetting the sorting.

  public void Clear()
  {
    mSortIndices.Clear();
  }Code language: C# (cs)

After that, we create a method called Add, which adds a new sprite renderer to the sorting list. Additionally, we set the rendering order for the sprite renderer. We have not yet implemented the SetRenderOrder method.

  public void Add(SpriteRenderer renderer)
  {
    mSortIndices.Add(renderer);
    SetRenderOrder(renderer, mSortIndices.Count);
  }Code language: C# (cs)

We now add the Remove method. This method removes a sprite renderer from the sorting list and updates the rendering order for the remaining sprite renderers in the list.

  public void Remove(SpriteRenderer renderer)
  {
    mSortIndices.Remove(renderer);
    for(int i = 0; i < mSortIndices.Count; i++)
    {
      SetRenderOrder(mSortIndices[i], i + 1);
    }
  }Code language: C# (cs)

After that, we implement the BringToTop Method. This method brings a specific sprite renderer to the top of the sorting order. It removes the sprite renderer from the list and then re-adds it, effectively placing it in the highest rendering order.

  public void BringToTop(SpriteRenderer renderer)
  {
    Remove(renderer);
    Add(renderer);
  }Code language: C# (cs)

Finally, we implement the SetRenderOrder Method. This private method sets the rendering order of a sprite renderer and adjusts its position on the z-axis to ensure correct sorting.

  private void SetRenderOrder(SpriteRenderer renderer, int index)
  {
    renderer.sortingOrder = index;
    Vector3 p = renderer.transform.position;
    p.z = -index / 10.0f;
    renderer.transform.position = p;
  }
}Code language: C# (cs)

We have implemented the TilesSorting class, which handles sorting sprite renderers in our jigsaw puzzle game to ensure the proper order of rendering of the tiles. We will now make use of this class. To do so, we open the Tile script file and add a new static variable called tilesSorting of type TilesSorting.

public class Tile
{
  // For tiles sorting.
  public static TilesSorting tilesSorting = new TilesSorting();
Code language: C# (cs)

 After that, we open the TileMovement script file and add a new variable called the mSpriteRenderer. This variable will keep a reference to the SpriteRenderer component. Create the Start method and store the reference to the SpriteRenderer component in this newly created variable.

public class TileMovement : MonoBehaviour
{
  public Tile tile { get; set; }
  private Vector3 mOffset = new Vector3(0.0f, 0.0f, 0.0f);

  private SpriteRenderer mSpriteRenderer;

  void Start()
  {
    mSpriteRenderer= GetComponent<SpriteRenderer>();
  }
Code language: C# (cs)

After that, call the BringToTop function of the TilesSorting class within the OnMouseDown method, with the mSpriteRenderer as the input parameter (add the highlighted lines below). Adding this ensures that whichever tile is clicked is brought to the top, which is what we want.

  private void OnMouseDown()
  {
    if (!GameApp.Instance.TileMovementEnabled) return;
    if(EventSystem.current.IsPointerOverGameObject())
    {
      return;
    }

    mOffset = transform.position - Camera.main.ScreenToWorldPoint(
      new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0.0f));

    // For sorting of tiles.
    Tile.tilesSorting.BringToTop(mSpriteRenderer);
  }
Code language: C# (cs)

Go to Unity editor and click Play. You should now be able to reorder a tile based on its selection. Click any jigsaw tile. This clicked tile now should be on top of all other tiles.

The Game App Singleton

We will now create an abstract Singleton class that can be used as a template framework for future singletons. Go to Unity editor and create a new script called Singleton in the scripts folder. Double-click and open it in Visual Studio or your favourite editor.

In computer programming, a singleton is a design pattern that restricts the instantiation of a class to only one object. It ensures that there is only a single instance of the class throughout the entire application and provides a global point of access to that instance.

Singletons are commonly employed in scenarios where global access to a single instance is preferable. In Unity game projects, singletons can be invaluable for providing global access to critical objects and game data that persists across multiple scenes. Singletons can also aid performance optimisation by reducing the overhead associated with redundant object creation and destruction. 

While singletons offer significant benefits in Unity game development, it’s crucial to use them judiciously to avoid potential drawbacks such as tight coupling and global state proliferation. We recommend using only one singleton object for a game project. 

We start by defining a namespace called Patterns and declaring a generic abstract class named Singleton. This class is intended to be a singleton, meaning that only one instance can exist throughout the application. It’s constrained to accept only types that derive from Component in Unity.

We declare a private static field s_instance of type T. This field will hold the class’s only instance.

namespace Patterns
{
  public abstract class Singleton<T> : MonoBehaviour where T : Component
  {
    private static T s_instance;Code language: C# (cs)

We then define the Instance property. This property is responsible for returning the only instance of the class. It’s implemented using a getter method. If the s_instance is null, it tries to find an existing instance of type T in the scene using FindObjectOfType. If no instance is found, it creates a new GameObject, assigns it the name of the type T, adds a component of type T to it, and sets s_instance to this new component. Finally, it returns the s_instance.

    public static T Instance
    {
      get
      {
        if(s_instance == null)
        {
          s_instance = FindObjectOfType<T>();
          if(s_instance == null)
          {
            GameObject obj = new GameObject();
            obj.name= typeof(T).Name;
            s_instance = obj.AddComponent<T>();
          }
        }
        return s_instance;
      }
    }Code language: JavaScript (javascript)

We now move on to the Awake method. Here, we ensure that only one instance of the singleton exists. If s_instance is null, it sets s_instance to the current instance, which is this, and makes the GameObject persistent across scene changes using DontDestroyOnLoad. If s_instance is not null, it destroys the GameObject to prevent multiple instances.

    protected virtual void Awake()
    {
      if(s_instance == null)
      {
        s_instance = this as T;
        DontDestroyOnLoad(gameObject);
      }
      else
      {
        Destroy(gameObject);
      }
    }
  }
}Code language: C# (cs)

This Singleton class can be inherited by other classes in Unity to ensure that only one instance of those classes exists throughout the application.

We now create another new script file called the GameApp. Double-click and open it in Visual Studio or your favourite editor. Remove MonoBehaviour and inherit from the Singleton class provided by the Patterns namespace we created before. This means that GameApp will implement the singleton pattern, ensuring that only one instance of GameApp exists throughout the application.

public class GameApp : Patterns.Singleton<GameApp>
{Code language: C# (cs)

After that, we declare three public properties for now. Later, we will extend this class based on our needs. First, we have the TileMovementEnabled property of type bool. This property represents whether tile movement is currently enabled in the game. Then, we declare another public property named SecondsSinceStart of type double. It represents the number of seconds that have elapsed since the start of the game.

Finally, we declare the TotalTilesInCorrectPosition property of type int. This property represents the total number of tiles currently in the correct position in the game.

  public bool TileMovementEnabled { get; set; } = false;
  public double SecondsSinceStart { get; set; } = 0;
  public int TotalTilesInCorrectPosition { get; set; } = 0;
}Code language: C# (cs)

The Game Menu

Now, we move on to creating the game menu. For this tutorial, we will keep our game simple. We will implement the user interface as shown on the screen. We will primarily have two sets of UI items. The first set contains only the Play button. This Play button is displayed only after all the jigsaw tiles are created. 

The second set includes the ShowHint button, the Exit button, the TilesInPlace versus the TotalNumberOfTile text fields, and the timer that shows the time that has elapsed since you started this jigsaw game. These UI elements are only shown after we click the Play button. The TileMovementEnabled property should only be enabled after all the tiles are shuffled. This is when the timer should also start.

Download the zip file called ui_assets, unzip and place it in the Resources folder of your Unity project. This folder contains some icons and a font that we will use for the tutorial.

Now, let’s create the UI elements. Go to the hierarchy window and add a new Canvas. 

Select the canvas game object from the hierarchy and go to the inspector. Go to the Canvas scaler section and change the UI Scale Mode to Scale with Screen Size. Change the values of Reference Resolution to 1600 by 900. 

Now select the canvas game object again, right-click, and add a Panel. Select this panel and rename it Top Panel. Go to Inspector and choose the Anchor Preset to Top-Centre. Set the x and y to be 0 and -75, respectively. Then, set the width to 1600 and the height to 75.

Right-click on the Top Panel and add a new image UI element. Name it Image Close. Select this image, go to the Inspector, and choose the Anchor Preset to Top-Right. Set the x and y to be -75 and -40, respectively. Then, set the width to 70 and the height to 70. Drag and drop the icon_blue sprite to the Source Image field.

Now, select this ImageClose game object, right-click it, and add a new legacy button UI element. Select this button, go to the Inspector, and choose the Anchor Preset to Center-Center. Set the x and y to be 0 and 0, respectively. Then, set the width to 60 and the height to 60. Drag and drop the icon_close sprite to the Source Image field.

Once again, right-click on the Top Panel and add a new image UI element. Name it ImageHint. Select this image, go to the Inspector, and choose the Anchor Preset to Top-Right. Set the x and y to be -75 and -125, respectively. Then, set the width to 70 and the height to 70. Drag and drop the icon_blue sprite to the Source Image field.

Now, select this ImageHint game object, right-click it, and add a new image UI element. Select this image, go to the Inspector, and choose the Anchor Preset to Center-Center. Set the x and y to be 0 and 0, respectively. Then, set the width to 60 and the height to 60. Drag and drop the icon_hint sprite to the Source Image field.

Select the Top Panel game object from the hierarchy, right-click it, and add a new image UI element. Name it ImageTilesCount. Select this image, go to the Inspector, and choose the Anchor Preset to Center-Center. Set the x and y to be 500 and 0, respectively. Then, set the width to 250 and the height to 70. Drag and drop the icon_blue sprite to the Source Image field.

Now, select the ImageTilesCount game object from the hierarchy and, right-click it, and add a new image UI element. Name this newly added image as ImageDark. Select this image, go to the Inspector, and choose the Anchor Preset to Center-Center. Set the x and y to be 60 and 0, respectively. Then, set the width to 115 and the height to 55. Then, click on the Color field of the Image and set the color to RGB to 100, 100, 200.

Once again, select the ImageTilesCount game object from the hierarchy, right-click it, and add a new image UI element. Name this newly added image “Image Light.” Select this image, go to the Inspector, and choose the Anchor Preset to Center-Center. Set the x and y to be -60 and 0, respectively. Then, set the width to 115 and the height to 55. Leave the image’s colour as white.

Once more, select the ImageTilesCount game object from the hierarchy, right-click it, and add two legacy text UI items. We will rename the first one to TextTilesInPlace. Go to the inspector and choose the Anchor Preset to Center-Center. Set the x and y to be -60 and 0, respectively. Then, set the width to 120 and the height to 60. Now, set the font type to MICROSS, Font Style to Bold, Font Size to 40 and alignment to centre. Set the default string to display on the text field to 0.

Rename the second text field to TextTotal Tiles. Go to the inspector and choose the Anchor Preset to Center-Center. Set the x and y to be 60 and 0, respectively. Then, set the width to 120 and the height to 60. Now, set the font type to MICROSS, Font Style to Bold, Font Size to 40, and Alignment to Center. Set the default string to display on the text field to 0.

Finally, we will now create the timer. To do so, select the “Top Panel” game object from the hierarchy and, right-click it, and add a new image UI element. Name this newly added image as ImageTimer. Select this image, go to the Inspector, and choose the Anchor Preset to Center-Center. Set the x and y to be -615 and 0, respectively. Then, set the width to 250 and the height to 70. Drag and drop the icon_blue sprite to the Source Image field.

Now, select the game object ImageTimer, right-click it, and add a text UI item. Rename it to TextTimer. Go to the inspector and choose the Anchor Preset to Center-Center. Set the x and y to be 0 and 0, respectively. Then, set the width to 200 and the height to 60. Now, set the font type to MICROSS, Font Style to Bold, Font Size to 40 and alignment to centre. Set the default string to display in the text field for 0 hours, 0 minutes, and 0 seconds. With this, we have completed the creation of the Top Panel.

Now, we move on to creating a bottom panel. Select the canvas game object from the hierarchy, right-click, and add a Panel. Select this panel and rename it BottomPanel. Go to Inspector and choose the Anchor Preset to Bottom-Centre. Set the x and y to be 0 and 75, respectively. Then, set the width to 800 and the height to 100.

Then, select this BottomPanel game object, right-click it, and add a new legacy button UI element. Select this button, go to the Inspector, and choose the Anchor Preset to Center-Center. Set the x and y to be 0 and 0, respectively. Then, set the width to 250 and the height to 80. Drag and drop the icon_play sprite to the Source Image field.

This concludes creating the canvas with the necessary UI elements. Finally, select the main camera from the hierarchy. Go to the inspector and change the Clear Flags field to Solid Color. Then, change the background color RGB to 50, 100, and 200.

Go to Unity editor, select the Game window and change the Aspect Ratio to 16 by 9. Click Play and see the newly implemented UI.

The Menu Script

We will now implement the necessary code for this canvas. 

Right-click on the project window and, in the scripts folder, create a new script called Menu. Double-click this script and open it in Visual Studio or your favourite editor. 

This Menu class is going to be responsible for managing some aspects of our game’s menu interface. We’re going to use this class to control various UI elements and handle user interactions.

In our Menu class, we declare a delegate named DelegateOnClick. This delegate represents a method that we can call when a button is clicked. Then, we declare a public field called btnPlayOnClick of type DelegateOnClick, which will hold a reference to a method that we want to execute when the play button is clicked.

public class Menu : MonoBehaviour
{
  public delegate void DelegateOnClick();
  public DelegateOnClick btnPlayOnClick;Code language: C# (cs)

Moving on, we declare some public variables to hold references to UI elements in our menu. These include panels panelTopPanel and panelBottomPanel and Text components textTime, textTotalTiles, and textTilesInPlace, which we will use to display information to the player.

  public GameObject panelTopPanel;
  public GameObject panelBottomPanel;
  public Text textTime;
  public Text textTotalTiles;
  public Text textTilesInPlace;Code language: D (d)

After this, we will implement a coroutine that will allow to fade in a panel containing UI elements. This method called FadeinUI is responsible for gradually fading in the UI elements of a specified panel over a given duration. We achieve this by adjusting the transparency of the UI elements over time. We start by setting the alpha of all UI elements to 0, then gradually increase it to 1 over the specified duration.

  IEnumerator FadeInUI(GameObject panel, float fadeInDuration = 2.0f)
  {
    Graphic[] graphics = panel.GetComponentsInChildren<Graphic>();
    foreach(Graphic graphic in graphics)
    {
      graphic.color = new Color(graphic.color.r, graphic.color.g, graphic.color.b, 0.0f);
    }
    float timer = 0.0f;
    while(timer < fadeInDuration)
    {
      timer += Time.deltaTime;
      float normalisedTime = timer / fadeInDuration;
      foreach(Graphic graphic in graphics)
      {
        graphic.color = new Color(graphic.color.r, graphic.color.g, graphic.color.b, normalisedTime);
      }
      yield return null;
    }
    foreach (Graphic graphic in graphics)
    {
      graphic.color = new Color(graphic.color.r, graphic.color.g, graphic.color.b, 1.0f);
    }
  }Code language: C# (cs)

Next, we have a couple of methods, SetEnableBottomPanel and SetEnableTopPanel, that enable or disable the visibility of our bottom and top panels, respectively. If the provided flag is true, we activate the corresponding panel GameObject and fade in its UI elements using our FadeInUI method.

  public void SetEnableBottomPanel(bool flag)
  {
    panelBottomPanel.SetActive(flag);
    if (flag)
    {
      FadeInUI(panelBottomPanel);
    }
  }
  public void SetEnableTopPanel(bool flag)
  {
    panelTopPanel.SetActive(flag);
    if (flag)
    {
      FadeInUI(panelTopPanel);
    }
  }Code language: C# (cs)

Moving on, we have a method called OnClickPlay. This method is called when the play button is clicked. We invoke the delegate btnPlayOnClick, triggering any method that has been assigned to it. This allows us to execute a custom behaviour when the play button is clicked.

  public void OnClickPlay()
  {
    btnPlayOnClick?.Invoke();
  }Code language: C# (cs)

We then implement the SetTimeInSeconds method. These methods will enable us to dynamically update the time that has elapsed since the start of this jigsaw puzzle in hours, minutes, and seconds. 

  public void SetTimeInSeconds(double tt)
  {
    System.TimeSpan t = System.TimeSpan.FromSeconds(tt);
    string time = string.Format("{0:D2} : {1:D2} : {2:D2}", t.Hours, t.Minutes, t.Seconds);
    textTime.text = time;
  }Code language: JavaScript (javascript)

After that, we implement two more methods. They are the SetTotalTiles and SetTilesInPlace. These methods display the total tiles count in the jigsaw puzzle and the total tiles in place count based on the current state of our game.

  public void SetTotalTiles(int count)
  {
    textTotalTiles.text = count.ToString();
  }
  public void SetTilesInPlace(int count)
  {
    textTilesInPlace.text = count.ToString();
  }Code language: C# (cs)

Shuffling of Tiles

To start playing our Jigsaw game, we will need to shuffle the tiles. Let’s go ahead and implement the shuffling of tiles in this sub-section. 

Double-click and open the BoardGen script in Visual Studio or your favourite editor.

We start by adding a few variables. First, we declare a public variable named menu of type Menu and initialise it with a value of null. Then, we declare a private list named regions. This list will serve as a collection to store rectangular regions used for positioning tiles while shuffling. 

public class BoardGen : MonoBehaviour
{


  *********
  // Access to the menu.
  public Menu menu = null;
  private List<Rect> regions = new List<Rect>();
Code language: C# (cs)

After that, we declare another private list named activeCoroutines, which will serve as a collection to store references to Coroutines. 

  private List<Coroutine> activeCoroutines = new List<Coroutine>();Code language: C# (cs)

We begin with a method called Coroutine_MoveOverSeconds. This coroutine is designed to move a GameObject smoothly from its current position to a specified destination over a given duration of time. It takes three parameters: the GameObject we want to move, the target position we want to move it to, and the duration of the movement in seconds.

  #region Shuffling related codes
  private IEnumerator Coroutine_MoveOverSeconds(GameObject objectToMove, Vector3 end, float seconds)
  {
    float elaspedTime = 0.0f;
    Vector3 startingPosition = objectToMove.transform.position;
    while(elaspedTime < seconds)
    {
      objectToMove.transform.position = Vector3.Lerp(
        startingPosition, end, (elaspedTime / seconds));
      elaspedTime += Time.deltaTime;
      yield return new WaitForEndOfFrame();
    }
    objectToMove.transform.position = end;
  }Code language: C# (cs)

First off, we initialise a couple of variables: elapsed Time to keep track of how much time has passed since the movement started and startingPosition to store the initial position of the object we’re moving.

Next, we enter a while loop that runs as long as the elapsed time is less than the specified duration. This loop ensures that the movement occurs smoothly over the desired duration.

Inside the loop, we use Vector3.Lerp to interpolate between the starting position and the target end position based on the ratio of elapsed time to total duration. This mechanism gradually moves the object from its starting position to the end position over time, creating a smooth transition.

We then increment the elapsed Time by the time that has passed since the last frame using Time.deltaTime. After that, we yield control back to the coroutine system for one frame.

Once the elapsed time exceeds the specified duration, the loop exits, and we set the object’s position to the end position to ensure that it reaches its final destination precisely. 

The Shuffle Method

Next, we implement the Shuffle method. This method is responsible for shuffling the position of a tile within predefined regions on the screen.

First, we check if the list regions is empty. If it is, we initialise it with two rectangular regions. These regions define areas on the screen where our tiles can be shuffled. They are positioned on the left and right sides of the screen, leaving some space between the tiles and the board.

  void Shuffle(GameObject obj)
  {
    if(regions.Count == 0)
    {
      regions.Add(new Rect(-300.0f, -100.0f, 50.0f, numTileY * Tile.tileSize));
      regions.Add(new Rect((numTileX+1) * Tile.tileSize, -100.0f, 50.0f, numTileY * Tile.tileSize));
    }Code language: JavaScript (javascript)

Next, we determine the final position of the tile after shuffling. We randomly select one of the regions from the regions list. Then, within the selected region, we generate a random point using the Random dot Range for both the x and y coordinates. This point represents the final position where we want to move the tile after shuffling.

    int regionIndex = UnityEngine.Random.Range(0, regions.Count);
    float x = UnityEngine.Random.Range(regions[regionIndex].xMin, regions[regionIndex].xMax);
    float y = UnityEngine.Random.Range(regions[regionIndex].yMin, regions[regionIndex].yMax);

After determining the final position, we create a Vector3 pos to hold the x, y, and z coordinates of the final position of the GameObject.

Now, we call the coroutine Coroutine_MoveOverSeconds to move the tile smoothly from its current position to the final position over a duration of 1 second. We start the coroutine by calling StartCoroutine and passing in the tile, the final position, and the duration of the movement as parameters.

    Vector3 pos = new Vector3(x, y, 0.0f);
    Coroutine moveCoroutine = StartCoroutine(Coroutine_MoveOverSeconds(obj, pos, 1.0f));
    activeCoroutines.Add(moveCoroutine);
  }Code language: C# (cs)

Finally, we keep track of the coroutine by adding it to the list we defined earlier called the activeCoroutines

The Shuffle Coroutine

After this, we now implement another coroutine called Coroutine_Shuffle. This coroutine is responsible for shuffling the positions of all the tiles in our game grid.

We start by iterating over each tile in our grid using nested for loops. The outer loop iterates over the rows, and the inner loop iterates over the columns.

  IEnumerator Coroutine_Shuffle()
  {
    for(int i = 0; i < numTileX; ++i)
    {
      for(int j = 0; j < numTileY; ++j)
      {
        Shuffle(mTileGameObjects[i, j]);
        yield return null;
      }
    }
    foreach(var item in activeCoroutines)
    {
      if(item != null)
      {
        yield return null;
      }
    }
    OnFinishedShuffling();
  }Code language: C# (cs)

Inside the nested loops, we call the Shuffle method we implemented earlier for each tile passing in the GameObject representing the tile. This Shuffle method is responsible for randomly positioning the tile within predefined regions on the screen, as we discussed earlier. After shuffling each tile, we yield control back to the coroutine system for one frame.

Once we have shuffled all the tiles in our grid, we wait for all the shuffle coroutines to finish before proceeding. We do this by iterating over the “active Coroutines” list, which contains references to all the shuffle coroutines that are currently running. For each coroutine in the list, we check if it’s still active and yield control back to the coroutine system for one frame using yield return null.

Finally, once all the shuffle coroutines have finished, we call the OnFinishedShuffling method which we are yet to implement. 

Then, we implement the method called ShuffleTiles. This method just calls the coroutine Coroutine_Shuffle.

  public void ShuffleTiles()
  {
    StartCoroutine(Coroutine_Shuffle());
  }Code language: C# (cs)

We will now implement the method called OnFinishedShuffling. This method is called after all the tiles have been shuffled. First, we clear the activeCoroutines list. 

  void OnFinishedShuffling()
  {
    activeCoroutines.Clear();Code language: JavaScript (javascript)

Next, we call the SetEnableBottomPanel method of the menu object to hide the bottom panel.

    menu.SetEnableBottomPanel(false);Code language: C# (cs)

Then, we start a coroutine called Coroutine_CallAfterDelay, which we have not yet implemented. This coroutine is used to delay the execution of a method. In this case, we are delaying the call to enable the top panel of the menu by 1 second. 

    StartCoroutine(Coroutine_CallAfterDelay(() => menu.SetEnableTopPanel(true), 1.0f));Code language: JavaScript (javascript)

After that, we enable tile movement in the game by setting the TileMovementEnabled property of the GameApp singleton instance to true.

    GameApp.Instance.TileMovementEnabled = true;Code language: C# (cs)

Next, we start a timer using the StartTimer method, which we have not yet implemented. After that, update the values displayed on our menu UI by setting the Total number of Tiles.

    StartTimer();
    menu.SetTotalTiles(numTileX * numTileY);
  }Code language: C# (cs)

Call After a Delay Coroutine

We will then implement the coroutine named Coroutine_CallAfterDelay. This coroutine takes two parameters: function, which is an action or method that we want to call after a delay, and delay, which is the amount of time we want to wait before calling the function.

  IEnumerator Coroutine_CallAfterDelay(System.Action function, float delay)
  {
    yield return new WaitForSeconds(delay);
    function();
  }Code language: C# (cs)

Inside the coroutine, we use yield return new WaitForSeconds method to wait for the specified delay. After waiting for the specified delay, we call the provided function by invoking it. 

The Timer Methods

Finally, we implement the StartTimer and other associated functions necessary to implement the timer.

We will start with a method called StartTimer. When StartTimer is invoked, it initiates a coroutine named Coroutime_Timer

  public void StartTimer()
  {
    StartCoroutine(Coroutine_Timer());
  }Code language: C# (cs)

Coroutine_Timer is a coroutine that continuously increments the elapsed time in 1-second intervals and updates the displayed time on the game menu UI.

  IEnumerator Coroutine_Timer()
  {
    while(true)
    {
      yield return new WaitForSeconds(1.0f);
      GameApp.Instance.SecondsSinceStart += 1;
      menu.SetTimeInSeconds(GameApp.Instance.SecondsSinceStart);
    }
  }Code language: C# (cs)

 Finally we implement the StopTimer function that halts the timer by stopping the coroutine. 

  public void StopTimer()
  {
    StopCoroutine(Coroutine_Timer());
  }
  #endregionCode language: C# (cs)

Before we can test and play our application from the Unity editor, we will need to enable the bottom panel display after we have created all the Jigsaw tiles, and we will also need to add the delegate to the play button on click event. So, go ahead and add the two calls in the Coroutine_CreateJigsawTiles method.

  IEnumerator Coroutine_CreateJigsawTiles()
  {
    ****
    // Enable the bottom panel and set the delegate to button play on click.
    menu.SetEnableBottomPanel(true);
    menu.btnPlayOnClick = ShuffleTiles;
Code language: C# (cs)

First, we add menu dot SetEnableBottomPanel to true, and then we set the delegate to button play on click event to ShuffleTiles method we created before. Setting this delegate will allow the shuffling of the jigsaw tiles as soon as we click on the play button in the bottom panel.

Go to the Unity editor. Select the canvas game object from the hierarchy window and go to the inspector. Set the specific fields in the menu script by dragging and dropping the UI elements, as shown here.

Then, select the ButtonPlay, go to the inspector and associate the on-click event with the function called OnClickPlay from the menu class.

Once you have associated all these UI elements with specific fields in the menu script, select the JigsawBoard game object from the hierarchy. Then, drag and drop the canvas to the menu field in the BoardGen script.

We now select both the TopPanel and the BottomPanel and hide these two UI elements from the inspector by default. We will enable these UI elements from the script based on when our we finish creating our Jigsaw Tiles and when we finish our shuffling.

Click Play. You should now see the jigsaw tiles being created. Once the tile creation is completed, the bottom panel with the Play button shows up. 

Click the Play button. You should now see the tiles being shuffled to the regions we defined. Once the shuffling is completed, we should see the top panel appear. You should now see the timer changing.


We will now proceed to implement the remaining functionality. We start with the show hint icon. We want to show the player the actual opaque image when the player clicks and holds this button. The image should be shown to the user as long as the user presses the left mouse button. We will need to implement a special script for this. Go ahead, right-click on the Projects window and in the scripts folder, create a new script called the HoldButton. Double-click and open in Visual Studio or your favourite editor.


The HoldButton class implements two interfaces: IPointerDownHandler and IPointerUpHandler. These interfaces are used to detect when the user presses down on the button and releases it.

We first declare a private boolean variable isPressed. This variable keeps track of whether the button is currently pressed down or not. 

Then, we add two Unity events called onPressAndHold and onRelease

to create a Jigsaw Puzzle Game”. We hope you have enjoyed the tutorial and have learned something new.

TO CONTINUE (in progress)

Pages: 1 2 3 4 5

6 thoughts on “Create a Jigsaw Puzzle Game in Unity”

    1. Hello Akshit. It’s good to hear that you found the tutorials helpful. I plan to complete the remaining part of the tutorial but couldn’t find the time to complete it. The remaining portions are loading and saving the game, the state machine to control the game states. The codes for this is available on my GitHub https://github.com/shamim-akhtar/jigsaw-puzzle repo.
      I intend to complete the remaining part of the tutorial by November.

  1. Hi!! Thanks for the tutorial. I would like to ask how to make to work with Render Texture?
    I would like to take the image for the puzzle from Render Texture.

Leave a Reply

Your email address will not be published. Required fields are marked *