3 minute read

If you’ve been dealing with Unity UI for a while, it’s very possible that you’ve had to interact with the Canvas component sorting order. Maybe in that rushed Game Jam project that you just threw “999” in there, or maybe in a more serious project that you actually tried to follow some rules for modifying it. The fact is that modifying the hierarchy sometimes is simply not enough.
So this leads to the question: how can we modify the Canvas sorting order in a reasonable and maintainable way?

Here’s how I’ve been dealing with it.

Implementation

First, we need to give names to our sorting layers. Magic numbers are really bad for maintainability and they make it harder to understand what’s going on, either in code or in the Unity Inspector. To address this, let’s create an enum to assist us. I’ll also add some values here for demonstration purposes, just make sure to change them to something that makes sense to your project.

Naming layers

// CanvasId.cs
public enum CanvasId
{
    LoadingScreen = 1,
    HUD = 2,
    Fullscreen = 3,
    UIEffects = 4,
}

Quick tip!

Always give numeric values to your serializable enums.
This prevents unexpected changes when renaming enum members.

This is already pretty helpful, you could just go and refactor your code to use the enum values for the sorting orders, this way we’ll be avoiding magic numbers.

But we can do better!

Ordering layers

Let’s create an ScriptableObject to make it easier to modify our sorting orders.

// CanvasSortingOrderSettings.cs
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class CanvasSortingOrderSettings : ScriptableObject
{
    const string ASSET_NAME = "CanvasSortingOrderSettings";

    public static CanvasSortingOrderSettings Instance
    {
        get
        {
            if (instance == null)
                instance = Resources.Load<CanvasSortingOrderSettings>(ASSET_NAME);
            return instance;
        }
    }
    static CanvasSortingOrderSettings instance;

    [SerializeField]
    List<CanvasId> canvasIds = new();

    public int GetSortingOrder(CanvasId id) => canvasIds.IndexOf(id);

    [MenuItem("Assets/Create/Canvas Sorting Order Settings")]
    public static void Create()
    {
        CanvasSortingOrderSettings asset = ScriptableObject.CreateInstance<CanvasSortingOrderSettings>();
        AssetDatabase.CreateAsset(
            asset,
            $"Assets/CanvasSortingOrder/Resources/{ASSET_NAME}.asset"
        );
        AssetDatabase.SaveAssets();
        EditorUtility.FocusProjectWindow();
        Selection.activeObject = asset;
    }
}

If you’re unfamiliar with ScriptableObjects, they’re used exclusively to store data; in this case we are storing a list of our CanvaIds. This script also adds a new MenuItem to create an instance of this ScriptableObject. Make sure to create this instance before proceeding.

The most important function here is GetSortingOrder(CanvasId id), and it’s a pretty simple one, it just grabs the index of that CanvasId inside our canvasIds list. This is the value that’ll be used later as the actual sorting order.

It should look like the image below after adding your elements:

Canvas Sorting Order Inspector

Now you can reorder your sorting layers easily by just moving the values up or down in the list!

Using an old Unity version?

Unity versions prior to 2020 didn’t allow for reordering list elements by default.
In this case you’ll need a custom editor to support reordering. Unfortunately it won’t be covered in this article.

Applying modifications

Now we just need to apply our named sorting layers to our canvases.
Let’s do that with a new component:

// CanvasSortingOrder.cs
using UnityEngine;

[RequireComponent(typeof(Canvas))]
public class CanvasSortingOrder : MonoBehaviour
{
    public CanvasId CanvasId
    {
        get => canvasId;
        set
        {
            canvasId = value;
            Canvas canvas = GetComponent<Canvas>();
            canvas.overrideSorting = true;
            canvas.sortingOrder = CanvasSortingOrderSettings.Instance.GetSortingOrder(canvasId);
        }
    }

    [SerializeField]
    CanvasId canvasId;

    void OnValidate()
    {
        CanvasId = canvasId;
    }
}

Now you just need to add this new component to your Canvas and modify the sorting order through the inspector drop down. It also allows you to edit the sorting order through code too, just remember to access the CanvasSortingOrder component instead of the Canvas one.

// bad - this won't consider the order you've defined in your CanvasSortingOrderSettings.
GetComponent<Canvas>().sortingOrder = (int)CanvasId.HUD;

// good!
GetComponent<CanvasSortingOrder>().CanvasId = CanvasId.HUD;

Conclusion

Check out the images below to see how it works in practice.
You can see the CanvasSortingOrder component next to each Canvas altering the sorting order. Notice that without changing the sorting orders, the “Loading” screen would appear below the “Fullscreen”.

HUD Fullscreen UI Loading Overlay

To sum it up: now you can give names to your sorting order layers and order them through the inspector.

Be advised that this is a very bare bones implementation, it uses a Singleton and loads assets from the Resources folder, which may or may not be interesting for your projects. It’s also missing some treatments for corner cases, like placing duplicate CanvasIds in your CanvasSortingOrderSettings.

Make sure to tweak it to fit your needs!