Sprite layer order determined by Y value

I’m working on a top-down sprite-based RPG in Unity and I’ve encountered somewhat of a problem: because of the 3/4 perspective I’m using, sometimes a player should be drawn above an object and other times, below. I’ve achieved this effect in MonoGame by simply sorting sprites by their Y position values and drawing in that order; however, in Unity this doesn’t appear to be possible. Is there any way to achieve this effect?

4 Answers

4

I ended up placing a function the following code in my update method:

GetComponent<SpriteRenderer>().sortingOrder = Mathf.RoundToInt(transform.position.y * 100f) * -1;

Thanks man, that helped me!

what if all characters are moving?! this need to be done in Update for all characters and it seems performance consuming!

The best answer (in year 2020) is by Muskar. In Unity: Edit -> Project Settings -> Graphics -> Transparency Sort Mode: Custom Axis + Transparency Sort Axis: X=0, Y=1, Z=0 Old thread, but since happened to not-read the whole thread I thought to add a comment here.

An old question, but now the answer is either to pivot all your sprites to the bottom and then do the global **Edit ** → Project SettingsGraphics → Transparency Sort Mode: Custom Axis + Transparency Sort Axis: X=0, Y=1, Z=0

But I wanted to do it by script:

using UnityEngine;

public enum SortingOrigin
{
    Pivot, Bottom, Top, Left, Right
}
public enum SortingAxis
{
    X, Y
}

[RequireComponent(typeof(Renderer))]
public class LayerSorter : MonoBehaviour {

    [SerializeField]
    private int _originOrder = 100;
    [SerializeField]
    private float _precision = 1f;
    [SerializeField]
    private int _offset = 0;
    [SerializeField]
    private bool _runOnlyOnce = false;
    [SerializeField]
    private SortingOrigin _sortingSelector;
    [SerializeField]
    private SortingAxis _sortingAxis = SortingAxis.Y;

    private Renderer _renderer;
    private float _timeLeft;
    private float _updateFrequency = .1f;

    private void Awake () {
        _renderer = GetComponent<Renderer>();
	}
    private void Start()
    {
        UpdateSortOrder();
        if (_runOnlyOnce)
        {
            enabled = false;
        }
    }

    private void LateUpdate ()
    {
        UpdateSortOrder();
    }

    private void UpdateSortOrder()
    {
        _timeLeft -= Time.deltaTime;
        if (_timeLeft <= 0)
        {
            _timeLeft = _updateFrequency;
            Vector2 pos = _renderer.bounds.center;
            float width = _renderer.bounds.size.x;
            float height = _renderer.bounds.size.y;
            switch (_sortingSelector)
            {
                case SortingOrigin.Bottom:
                    pos += Vector2.down * height / 2;
                    break;
                case SortingOrigin.Top:
                    pos += Vector2.up * height / 2;
                    break;
                case SortingOrigin.Left:
                    pos += Vector2.left * width / 2;
                    break;
                case SortingOrigin.Right:
                    pos += Vector2.right * width / 2;
                    break;
                default:
                    pos = transform.position;
                    break;
            }
            float posFromAxis = _sortingAxis == SortingAxis.X ? pos.x : pos.y;
            _renderer.sortingOrder = (int)(_originOrder - posFromAxis / _precision + _offset);
        }
    }
}

Here’s where I started: Learn game development w/ Unity | Courses & tutorials in game design, VR, AR, & Real-time 3D | Unity Learn

It also has some other good tips on sorting, like sorting groups.

This should be the accepted answer. Why would you do it by script when unity has a built-in option though?

Since this was one of the major results when googling a similar problem, I figure I’d link to my solution based on this answer.

http://forum.unity3d.com/threads/order-in-layer-16-bit-limit-huge-world-in-one-scene.331646/

Order In Layer is 16 bits, so OrderInLayer = Position.Z is limited to 32767,32767.

Unity gives a floating point limitation warning after 99999, which is a little more than 3x the OrderInLayer limitations.

The solution? Divide by 3!

If you change Order In Layer every 3 integers/position instead of every 1, it is really closer to the floating point limitation for positions (32767 * 3 = almost 99999). I tested this by changing Order In Layer every 5, and I didn’t notice any difference in my game. (1 position = 1 pixel). It’s a 3D world with 2D sprites and a perspective camera, so this was not easy after trying so many solutions. I tried other solutions as you can see in the thread (and more that I didn’t mention in the thread.)

Anyway, here is the code:

int pos = Mathf.RoundToInt(theParent.transform.position.z);
pos /= 3;
spriteRenderer.sortingOrder = (pos * -1) + OrderOffset;

What are you refering to with OrderOffset?

OrderOffset is not required. It's simply an integer which you can add to the equation so that you can offset the order by +/- any number. I use OrderOffsets because my characters are multiple sprites, so their legs may be behind their bodies & their heads infront of their bodies. So Body's OrderOffset = 0, Legs = -1, Head = 1, etc.

As someone is trying to say this is already solved by Unity, but if you are as me and need to have a visual description of the configuration here you are:

Edit > Project Settings > Graphics > Transparency Sort Mode: Custom Axis
Edit > Project Settings > Graphics > Transparency Sort Axis: X=0, Y=1, Z=0

192746-screenshot-2022-02-17-at-170506.png

If you are using Tiles I also recommend to configure:

Tilemap Renderer > Mode > Individual

192747-screenshot-2022-02-17-at-170156.png