2
\$\begingroup\$

This is a naive implementation of a Z-sorting in order to fake 3D depth using sprites in a 2D scene. I would appreciate feedback on this, especially for performance as this has to be called each frame to handle moving objects.

A ZIndexHandler is added to every object that may need to be sorted. When an object is loaded in the scene, OnEnable is automatically called by Unity3D. When the object is removed or hidden, OnDisable is called.

Each object registers and unregisters itself from the ZIndexManager depending on whether it needs to get sorted on not.

public class ZIndexHandler : MonoBehaviour
{
    //A transform is used to place the position offset of the object
    //So that PositionOnScreen is not the middle of the sprite
    [SerializeField]
    private Transform positionOnScreenTransform;
    public Transform PositionOnScreenTransform
    {
        set { positionOnScreenTransform = value; }
        get { return positionOnScreenTransform; }
    }

    public Vector3 PositionOnScreen
    {
        get { return positionOnScreenTransform.position; }
    }

    //If true, this object won't get sorted.
    [SerializeField]
    private bool ignoreZBuffer;
    public bool IgnoreZBuffer
    {
        get { return ignoreZBuffer; }
        set
        {
            ignoreZBuffer = value;
            if(!ignoreZBuffer)
                updateHandlerActivy(true);
        }
    }

    //Pointer to the ZIndexManager singleton
    private ZIndexManager indexManagerInstance;

    //The sprites in this objects are all stored here once and for all
    protected List<SpriteRenderer> sprites;

    protected virtual void Awake()
    {
        sprites = new List<SpriteRenderer>();

        //Recursively search for sprite in the object's hierarchy
        findAndStoreSpritesRecursive(transform);
    }

    void OnEnable()
    {
        indexManagerInstance = ZIndexManager.Instance;
        updateHandlerActivy(!ignoreZBuffer);
    }

    void OnDisable()
    {
        updateHandlerActivy(false);
    }

    //Register or unregister this object to the Z-sorting process
    private void updateHandlerActivy(bool needSort)
    {
        if (indexManagerInstance)
        {
            if(needSort)
                indexManagerInstance.registerObject(this);
            else
                indexManagerInstance.removeObject(this);
        }
    }

    private void findAndStoreSpritesRecursive(Transform curTransform)
    {
        SpriteRenderer sr = curTransform.GetComponent<SpriteRenderer>();
        if (sr != null)
            sprites.Add(sr);
        foreach (Transform t in transform)
            findAndStoreSpritesRecursive(t);
    }

    public void setZIndex(int index)
    {
        foreach (SpriteRenderer s in sprites)
            s.sortingOrder = index;
    }

    public override bool Equals(object other)
    {
        return GetInstanceID() == ((ZIndexHandler)other).GetInstanceID();
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
}

The ZIndexManager class is a singleton that handles the sorting.

public class ZIndexManager : MonoBehaviour
{
    List<ZIndexHandler> screenObjects = new List<ZIndexHandler>();

    //The Z index starts at 10 to leave some space for the background
    public static int StartIndex = 10;

    //Singleton implementation using FindObjectOfType
    private static ZIndexManager instance;
    public static ZIndexManager Instance
    {
        get
        {
            if (instance == null)
                instance = FindObjectOfType<ZIndexManager>();
            return instance;
        }
    }

    public void registerObject(ZIndexHandler obj)
    {
        screenObjects.Add(obj);
    }

    public void removeObject(ZIndexHandler obj)
    {
        if(screenObjects.Contains(obj))
            screenObjects.Remove(obj);
    }

    public void sortZBufferOnList(ref List<ZIndexHandler> list)
    {
        //Put everything in the correct order based on the Y position of the object
        list.Sort(delegate (ZIndexHandler zi1, ZIndexHandler zi2)
        {
            if(zi1 && ! zi2)
                return -1;
            else if(!zi1)
                return 1;

            Vector3 p1 = zi1.PositionOnScreen;
            Vector3 p2 = zi2.PositionOnScreen;
            if (p1.y == p2.y) return 0;
            return -1 * p1.y.CompareTo(p2.y);
        });

        //Iterate over each object to update their Z index
        for (int i = 0; i < list.Count; ++i)
            list[i].setZIndex(StartIndex + i);
    }

    void Update()
    {
        sortZBufferOnList(ref screenObjects);
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.