Z-Ordering Sprites in Unity

Second post. Only took nine months. Not bad. Not bad at all. Well, we’ve finally started in earnest on our game. Right now we have the basics set up: a player character controllable by a gamepad, some collidable environment objects, some interactable elements, and a simple NPC walking around. We haven’t reached the point where we have a fun game or even game mechanic yet, but we have a solid framework in place which will let us test ideas quickly.

Part of that framework is a simple script we wrote to help us with the layering of 2D sprites in our scene. By default, Unity provides a set of layers and within each layer you can specify sort order on individual sprites. This system works well and is flexible since you can programmatically set either property. Here’s an example of what happens when you specify sort order on a sprite and move it around the scene.

Bad ordering
That animation looks strangely familiar…

It works fine if the player is behind the counter, but as soon as he is in front of it, he is still behind it. We can make the player’s ‘Order in Layer’ property higher than the counter’s, but then we’ll have the inverse problem.

To fix this, we’ll assign a layer order dynamically based on the position.y value of the sprite in question. In C#, this looks like:

Sprite = GetComponent<SpriteRenderer>();
Sprite.sortingOrder = -Mathf.RoundToInt((transform.position.y + AnchorOffset) / 0.05f);

The magic number of 0.05f specifies how many tick marks we want to have in the Y-direction. The smaller the number, the more subdivisions we’ll have. 0.05f was the resolution that worked well for our game. The AnchorOffset variable above is exposed as a public float so you can tweak it in editor. To best illustrate why it’s needed, here it is set to 0:

anchorzero
A bit tight in here

Now let’s set it to -0.1:

anchornegative
So roomy

If this script is on all game objects with sprite renderers, the order in layer will be set solely on the transform position which is generally the center of the sprite. Sometimes we want to specify that the part of the sprite which dictates the  layer order is not at the center. So by setting AnchorOffset to -0.1f, we are saying that the sprite’s feet are what is grounding the sprite.

Now let’s see our friend walk around the counter.

good
Where’d the register go?

As mentioned, if we put this on every game object with a sprite renderer, everything will work out. Here’s our friend awkwardly stalking a buddy:

goodai
‘Hello good sir’

Notice that he can walk around the NPC and he’s occluded as expected.

To prevent having code run on every sprite renderer every Update, we can specify that some sprite renderers only get their order set in Start and then never again. This works well for environment sprites. We expose this publicly as IsStatic. Note that we should eventually just ‘bake in’ the order after we are sure a sprite isn’t going to move. However, right now we’re moving level bits around a lot so it’s easier to have things work, despite the trivial performance hit. When we’re ready, we’ll run the game, see what layer order a static sprite gets in the inspector and then save that as the layer order and delete this script from the game object. Alternatively, we’ll forget about this step and not worry too much about the bool check every Update per sprite renderer.

Here’s the complete script file:

using UnityEngine;
 
public class SpriteZOrder : MonoBehaviour
{
    public bool IsStatic;
    public float AnchorOffset;
 
    private SpriteRenderer Sprite;
 
    void Start()
    {
        Sprite = GetComponent<SpriteRenderer>();
        AssignSortOrder();
    }
 
    void Update()
    {
        if (!IsStatic)
        {
            AssignSortOrder();
        }
    }
 
    private void AssignSortOrder() 
    {
        Sprite.sortingOrder = -Mathf.RoundToInt((transform.position.y + AnchorOffset) / 0.05f);
    }
}

Leave a Reply

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