Runtime Depth Sorting of Sprites in a Layer

Runtime Depth Sorting of Sprites in a Layer

In this tutorial, we will learn to implement runtime depth sorting of sprites in a layer.

Typically, you can associate a RenderOrder to a sprite in Unity editor. However, there may be situations where you would want to reorder the sprite render order, and set the correct z value at runtime based on the sprite that you select or pick in-game.

For example, in a Jigsaw game, all the tiles can be in the same layer with the same depth. However, based on which tile you pick, you 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 other tiles.

By the end of this tutorial, we want to achieve the following:

  1. Click a sprite and bring it to the top,
  2. Click and drag to move a sprite,
  3. Click and pan camera when the click on not on any sprite, and
  4. Slider to zoom-in and zoom-out of camera (optional buttons to do the same)

Contact me

Find the GitHub repository of this tutorial at https://github.com/shamim-akhtar/sorting-sprites-renderorder.

Click to try the WebGL version below. NOTE: The WebGL version might not work for mobile devices.

The Unity Project

Download the starter package for this tutorial. Create a new Unity2D project and import the starter-package.package file.

Your scene should look like the below picture.

The project loaded in Unity after you have imported the starter-package.package asset.

Brief Description of the Starter Package

The starter package comes with the camera pan and zoom-in/out script and the UI for doing the same. I have created a separate tutorial on how to Implement Camera Pan and Zoom Controls in Unity2D.

So from the objectives of this tutorial, we already have the 3 and 4 implemented with this starter package. We will, however, need to amend a bit so that the camera does not pan when we interact with the tiles (sprites).

Besides the above, you can also see that there are two game objects. One of them is the Background, and the other one is called Tiles. The Tiles game object contains 10 sprites, all at the same position.

Open the Tags and Layers window, and you will find that I have added two layers in the Sorting Layers and Layers sections, as shown below.

The Tags and Layers window showing the new layers added to the project.

For this tutorial, we will be more interested in Sorting Layers. You can infer that the Background layer will be behind the Tiles layer based on the above diagram. For this tutorial, there is no value for our Background layer. We have created the Background game object and the Background sorting layer to give context to our scene.

You will also observe that all the ten tiles (children of Tiles game object) are in the same sorting layer called the Tiles. Since the position of each tile has the same depth (0.0), to define the depth order, we will need to set the Order in Layer value accordingly.

The below image shows the values of tile 1 in the Inspector.

This image shows the Sorting Layer name and the Order in layer for tile 1.

Similarly, you can define the Order in Layer values for the other nine tiles by assigning numbers between 0 to 9. This configuration is as far as it goes from the Unity editor setup point of view.

We will now start our implementation for achieving the first two objectives.

Click and drag to move a sprite

Let’s add the functionality to click, select and drag a sprite to move the tiles at runtime. Please create a new C# script and name it TileMovement. Double-click on TileMovement and open in Visual Studio.

The Variables

Add the following variables.

// Global setting to disable tile movement. public static bool TileMovementEnabled { get; set; } = true; #region Private variables // cache the sprite renderer. SpriteRenderer mSpriteRenderer; // a variable to store the offset // when we click on the tile. This variable // is needed to implement drag functionality // of the tile/sprite. Vector3 mOffset = new Vector3(0.0f, 0.0f, 0.0f); #endregion
Code language: C# (cs)

The Start Method

In the Start method, we cache the SpriteRenderer.

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

On Mouse Down

Next, we implement the OnMouseDown function.

void OnMouseDown() { if (EventSystem.current.IsPointerOverGameObject()) { return; } if (!TileMovementEnabled || !enabled) return; // Hit piece. So disable the camera panning. CameraManipulator2D.IsCameraPanning = false; mOffset = transform.position - Camera.main.ScreenToWorldPoint( new Vector3( Input.mousePosition.x, Input.mousePosition.y, 0.0f)); }
Code language: C# (cs)

In the above implementation, we first check if the click is on any UI item by calling EventSystem.current.IsPointerOverGameObject() method. If the click is on UI, then we return from the function.

Then we check if the tile movement itself is enabled. If not, then once again, we return from the function.

Next, we set the global setting for the camera to not pan by assigning CameraManipulator2D.IsCameraPanning to false. We do so because we do not want the camera to pan when we are dragging sprites.

Finally, we save the clicked point to our mOffset variable.

On Mouse Drag

void OnMouseDrag() { if (EventSystem.current.IsPointerOverGameObject()) { return; } if (!TileMovementEnabled || !enabled) 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)

The codes in the above function should be self-explanatory.

On Mouse Up

void OnMouseUp() { if (EventSystem.current.IsPointerOverGameObject()) { return; } if (!TileMovementEnabled || !enabled) return; // Enable back the camera panning. CameraManipulator2D.IsCameraPanning = true; }
Code language: C# (cs)

Select all the tiles (1 to 10) in the Unity editor and add the TileMovement script component and the BoxCollider2D component.

Click Play to run and test the application at this stage.

You should be able to select and drag a tile. When your click is not on any tile, then the camera will pan. However, there is a problem. Can you identify the problem?

Problem

You should observe that sometimes when we click and select a tile, the tile from behind gets selected. This phenomenon happens because all the tiles have the same z value, which is 0. Our Raycast for picking might not pick up the one which we see visually on top.

We now have two problems to solve. Not only should our render order be correct, but we also need to make our z value correct so that our picking picks up the topmost tile if more tiles are overlapping.

Click a sprite and bring it to the top

We want to dynamically change the order layer when we click on any of these tiles. If we click on tile 8, we want tile 8 to be on top of all the other tiles.

Create a new C# file and name it TilesSorting. Double-click and open the file in Visual Studio. We want this class to be a pure C# class.

We want to keep a list of the sprites that we need to do depth sorting.

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

Next, we implement the essential functions of adding a tile, removing a tile, and clearing the tiles from the internal list.

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

The SetRenderOrder Method

Next, we implement the SetRenderOrder method. In this method, we solve both the render order and the depth (z value) issues.

void SetRenderOrder(SpriteRenderer ren, int order) { // First we set the render order of sorting. ren.sortingOrder = order; // Then we set the z value so that selection/raycast // selects the top sprite. Vector3 p = ren.transform.position; p.z = -order / 10.0f; ren.transform.position = p; }
Code language: C# (cs)

We will make SetrenderOrder private. The function takes in the SpriteRenderer and the render order as input parameters.

In the first highlighted line, we take care of the render order. This render order value will visually bring up the sprite. However, for picking this is not enough.

So, we set a z value to the sprite renderer’s transform component. I have put a constant value of 10 that divides the render order. You can play around with this value and set a different value. You may even not divide the value and directly negate the order and set that as the z value.

NOTE: Remember, if you have 1000 sprites and use 10 as the constant value for the divisor, then the last value for the sprite will be -100. So you must make sure that the camera is further than this value; else, view frustum culling will cull off your sprites that fall behind the camera.

Finally, we make a public method called BringToTop with the SpriteRenderer as the input parameter.

public void BringToTop(SpriteRenderer ren) { // Find the index of ren. Remove(ren); Add(ren); }
Code language: C# (cs)

Enabling Tile Sorting

Open up the TileMovement script file in Visual Studio. We will now do the necessary binding of tile movement and the tiles sorting.

Add a private static variable to store the reference to TilesSorting.

static TilesSorting sTilesSorting = new TilesSorting();
Code language: JavaScript (javascript)

Modify the Start method as follows:

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

We will add the SpriteRenderer to the sTilesSorting so that the sorter can sort the tile.

Then in the OnMouseDown method, add the highlighted line below.

void OnMouseDown() { if (EventSystem.current.IsPointerOverGameObject()) { return; } if (!TileMovementEnabled || !enabled) return; // Hit piece. So disable the camera panning. CameraManipulator2D.IsCameraPanning = false; sTilesSorting.BringToTop(mSpriteRenderer); mOffset = transform.position - Camera.main.ScreenToWorldPoint( new Vector3( Input.mousePosition.x, Input.mousePosition.y, 0.0f)); }
Code language: C# (cs)

That completes our tutorial. Click Play and run the application. You can now select and bring the tile up visually and for our raycast to pick the correct tile. See the video below.

Read My Other Tutorials

  1. Implement Constant Size Sprite in Unity2D
  2. Implement Camera Pan and Zoom Controls in Unity2D
  3. Implement Drag and Drop Item in Unity
  4. Graph-Based Pathfinding Using C# in Unity
  5. 2D Grid-Based Pathfinding Using C# and Unity
  6. 8-Puzzle Problem Using A* in C# and Unity
  7. Create a Jigsaw Puzzle Game in Unity
  8. Implement a Generic Pathfinder in Unity using C#
  9. Create a Jigsaw Puzzle Game in Unity
  10. Generic Finite State Machine Using C#
  11. Implement Bezier Curve using C# in Unity
  12. Create a Jigsaw Tile from an Existing Image
  13. Create a Jigsaw Board from an Existing Image
  14. Solving 8 puzzle problem using A* star search
  15. A Configurable Third-Person Camera in Unity
  16. Player Controls With Finite State Machine Using C# in Unity
  17. Finite State Machine Using C# Delegates in Unity
  18. Enemy Behaviour With Finite State Machine Using C# Delegates in Unity
  19. Augmented Reality – Fire Effect using Vuforia and Unity
  20. Implementing a Finite State Machine Using C# in Unity
  21. Solving 8 puzzle problem using A* star search in C++
  22. What Are C# Delegates And How To Use Them
  23. How to Generate Mazes Using Depth-First Algorithm

Leave a Reply

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