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:
- Click a sprite and bring it to the top,
- Click and drag to move a sprite,
- Click and pan camera when the click on not on any sprite, and
- 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.
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.
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.
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
- Implement Constant Size Sprite in Unity2D
- Implement Camera Pan and Zoom Controls in Unity2D
- Implement Drag and Drop Item in Unity
- Graph-Based Pathfinding Using C# in Unity
- 2D Grid-Based Pathfinding Using C# and Unity
- 8-Puzzle Problem Using A* in C# and Unity
- Create a Jigsaw Puzzle Game in Unity
- Implement a Generic Pathfinder in Unity using C#
- Create a Jigsaw Puzzle Game in Unity
- Generic Finite State Machine Using C#
- Implement Bezier Curve using C# in Unity
- Create a Jigsaw Tile from an Existing Image
- Create a Jigsaw Board from an Existing Image
- Solving 8 puzzle problem using A* star search
- A Configurable Third-Person Camera in Unity
- Player Controls With Finite State Machine Using C# in Unity
- Finite State Machine Using C# Delegates in Unity
- Enemy Behaviour With Finite State Machine Using C# Delegates in Unity
- Augmented Reality – Fire Effect using Vuforia and Unity
- Implementing a Finite State Machine Using C# in Unity
- Solving 8 puzzle problem using A* star search in C++
- What Are C# Delegates And How To Use Them
- How to Generate Mazes Using Depth-First Algorithm
A committed and optimistic professional who brings passion and enthusiasm to help motivate, guide and mentor young students into their transition to the Industry and reshape their careers for a fulfilling future. The past is something that you cannot undo. The future is something that you can build.
I enjoy coding, developing games and writing tutorials. Visit my GitHub to see the projects I am working on right now.
Educator | Developer | Mentor