[C#] object on top with highest sortingOrder is not selected first

I have arrange a number of objects on top of each other were the highest object have the highest sortingOrder, all objects have unique sortorder numbers. All objects have z = 5.

I use the following ray cast:

hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);



			if(hit.collider != null) {

				if(hit.collider.tag != "MultipleIcon") {
					print ("hit.collider.tag = " + hit.collider.tag);

The problem is that despite the above when i click on the top object another object a bit down with a lower sortingorder is selected.

Anyone that have any idea what can be wrong here?

Pretty sure that the Sprite Renderer’s Sorting Layer and Order in Layer have no effect on raycasts.

1 Like

There are three types of “layers” in Unity which is a bit confusing I know but they all have their purpose. Here is a checklist of sorts (pun intended) to help you on your way.

Type 1: Z-Layer
1618251--98799--$z-layer.PNG

This type of layer is actually just a z coordinate (or plane when not also referring to x and/or y coordinates) z distance from the arbitrary origin. If you place a new default Unity camera in the scene then the more positive the z value the further from the camera an object is. As an example if the camera is at z = -10 and pointing along the z axis toward z = posInfinity then an object at z = 0 is in front of an object at z = 5.

Type 2: Layers (as in strictly called “Layers” in Unity)
1618251--98800--$layers.PNG

This type of layer is for…well, actually anything you want, but can be tied into the physics system if you want it to be so.

Here is how (this is 2D but 3D is almost identical):
1618251--98801--$physics2D.PNG

Two important notes here are to look at the “Ignore Raycast Layer” to ensure the layer listed next to “Tag” in the inspector on your object does not fall in the list of layers that ignore raycasts. By default all layers ignore raycasting as seen above.
The other important thing to note is the checkbox for “Raycasts Hit Trigger”. If your object is a trigger collider and this box is not checked then you cannot use raycasts to click on that object.

Type 3: Sorting Layers
1618251--98798--$Capture.PNG

These layers have absolutely no affect on physics and are there only to draw sprites in the order you want regardless of where they are positioned in relation to one another and the camera. From your post I don’t think this is what you are talking about but I mention them just in case.

So to address your question more specifically, you are trying to cast a 3d ray into 2d space using a 2d method which will not work correctly. See this post.
The Camera.main.ScreenToWorldPoint(Input.mousePosition) in your code returns a Vector3 and the parameter it is used for expects a Vector2 so Unity will convert it to a Vector2. You are then casting the ray along Vector2.zero which will all have the result of casting a ray from the mouse point to the origin in a 2d plane and not into the z-axis as you might think.

Here is line drawing using what you have above:
1618251--98802--$line.PNG

As you can see the ray will travel in a flat 2d plane from the mouse point to the origin. The camera icon is at the origin.
As the post at the hyperlink shows you will need to use Physics2D.OverlapPointAll in order to achieve what you are going for since you are working in 2d.

Also, remember that sorting layer will have no affect, your objects must be on different z layers even though you are working in 2d or a random first object found will be returned. Keep this in mind if you use Physics2D.OverlapPoint which provides min and max depths to check.

Good luck! :slight_smile:

As Shubius explained, Raycast has no sense of Sprite SortingOrder. Here’s a script I whipped up though that will loop through all the Sprites under the current mouse point, that are in the defined layers of your choice, and return what the closest Sprite is in terms of SortingOrder.

	private LayerMask clickableLayers;
	private RaycastHit2D[] hits; //Change this number to however many selectable objects you think you'll have layered on top of eachother. This is for performance reasons.
	private float rayStart = -2; //Start Raycast from this Z-Coordinate
	private float rayEnd = 5;  //End Raycast at this Z-Coordinate
	private Vector3 clickedPos;
	private GameObject selectedObject;
	
	void Start () 
	{
		clickableLayers = (1 << LayerMask.NameToLayer("Tiles")) //Set the layers you want to be clickable here, duplicate the lines for however many you need
						|(1 << LayerMask.NameToLayer("Ground"))
						|(1 << LayerMask.NameToLayer("SomeMoreClickableObjects"));
	}
	
	void Update () 
	{
		if(Input.GetButtonDown("Fire1"))
		{
			SelectTopTile(); //This is how you can call it at any time
		}
	}
	
	void SelectTopTile()
	{
		clickedPos = Camera.main.ScreenToWorldPoint (Input.mousePosition);
		hits = Physics2D.LinecastAll(new Vector3(clickedPos.x,clickedPos.y,rayStart),new Vector3(clickedPos.x,clickedPos.y,rayEnd),clickableLayers); //Cast ray at the world space the mouse is at
		
		if(hits.Length > 0) //Only function if we actually hit something
		{
			int topHit = 0; //Set our top hit to a default of the first index in our "hits" array, in case there are no others
			int preValue = hits[0].transform.GetComponent<SpriteRenderer>().sortingOrder; //Set our first compare value to the SortingOrder value of the first object in the array, so it doesn't get skipped
			
			for (int arrayID = 1; arrayID < hits.Length; arrayID++) //Loop for every extra item the raycast hit
			{
				int tempValue = hits[arrayID].transform.GetComponent<SpriteRenderer>().sortingOrder; //Store SortingOrder value from the current item in the array being accessed
				
				if (tempValue > preValue) //If the SortingOrder value of the current check is higher than the previous lowest
				{
					preValue = tempValue; //Set the "Previous Value" to the current one just changed, for comparison later in loop
					topHit = arrayID; //Set our topHit with the Array Index value of the current Array item, since it currently has the highest/closest SortingOrder value
				}
			}
			selectedObject = hits[topHit].transform.gameObject;
		}
	}

When called, it will assign “selectedObject” whatever the closest object under your mouse cursor is, in terms of SortingOrder.

And here’s a version that will also take the SortingLayer into account:

	private LayerMask clickableLayers;
	private RaycastHit2D[] hits; //Change this number to however many selectable objects you think you'll have layered on top of eachother. This is for performance reasons.
	private float rayStart = -2; //Start Raycast from this Z-Coordinate
	private float rayEnd = 5;  //End Raycast at this Z-Coordinate
	private GameObject selectedObject;
	
	void Start () 
	{
		clickableLayers = (1 << LayerMask.NameToLayer("Tiles")) //Set the layers you want to be clickable here, duplicate the lines for however many you need
						|(1 << LayerMask.NameToLayer("Ground"))
						|(1 << LayerMask.NameToLayer("SomeMoreClickableObjects"));
	}
	
	void Update () 
	{
		if(Input.GetButtonDown("Fire1"))
		{
			SelectTopTile(); //This is how you can call it at any time
		}
	}
	
	void SelectTopTile()
	{
		Vector3 clickedPos = Camera.main.ScreenToWorldPoint (Input.mousePosition); //Store out clicked position
		hits = Physics2D.LinecastAll(new Vector3(clickedPos.x,clickedPos.y,rayStart),new Vector3(clickedPos.x,clickedPos.y,rayEnd),clickableLayers); //Cast ray at the world space the mouse is at
		
		if(hits.Length > 0) //Only function if we actually hit something
		{
			int topHit = 0; //Set our top hit to a default of the first index in our "hits" array, in case there are no others
			int preVal = hits[0].transform.GetComponent<SpriteRenderer>().sortingLayerID; //Store the SortingLayerID of the first object in the array, so it doesn't get skipped
			int preSubVal = hits[0].transform.GetComponent<SpriteRenderer>().sortingOrder; //Store the SortingOrder value of the first object in the array, in case it needs to be compared to
			
			for (int arrayID = 1; arrayID < hits.Length; arrayID++) //Loop for every extra item the raycast hit
			{
				int curVal = hits[arrayID].transform.GetComponent<SpriteRenderer>().sortingLayerID; //Store SortingLayerID of the current item in the array being accessed
				
				if (curVal < preVal) //If the SortingLayerID of the current array item is lower than the previous lowest
				{
					preVal = curVal; //Set the "Previous Value" to the current one since it's lower, as it will become the "Previous Lowest" on the next loop
					topHit = arrayID; //Set our topHit with the Array Index value of the current closest Array item, since it currently has the highest/closest SortingLayerID
					preSubVal = hits[arrayID].transform.GetComponent<SpriteRenderer>().sortingOrder; //Store SortingOrder value of the current closest object, for comparison next loop if we end up going to the "else if"
				}
				else if( (curVal == preVal)  (hits[arrayID].transform.GetComponent<SpriteRenderer>().sortingOrder > preSubVal) ) //If SortingLayerID are the same, then we need to compare SortingOrder. If the SortingOrder is lower than the one stored in the previous loop, then update values
				{
					topHit = arrayID;
					preSubVal = hits[arrayID].transform.GetComponent<SpriteRenderer>().sortingOrder;
				}
			}
			selectedObject = hits[topHit].transform.gameObject;
		}
	}

I think this is still an issue, or at least I did not find a built-in solution (is there one I missed?). However, I took Invertex’s code and structured it in in a way that you can call it like a normal Physics2D.Raycast:

public static RaycastHit2D Raycast2DWithOrder (Vector2 origin, Vector2 direction, float distance = Mathf.Infinity, int layerMask = Physics2D.DefaultRaycastLayers) {
            RaycastHit2D[] hits = Physics2D.RaycastAll(origin, direction, distance, layerMask);
       
            if(hits.Length > 0) //Only function if we actually hit something
            {
                int closestItem = 0; //Set our top hit to a default of the first index in our "hits" array, in case there are no others
                int lowestLayerID = int.MaxValue;
                int highestSortingOrder = int.MaxValue;
           
                for (int i = 1; i < hits.Length; i++) //Loop for every extra item the raycast hit
                {
                    SpriteRenderer myRenderer = hits[i].transform.GetComponent<SpriteRenderer>();
                    if(myRenderer == null) {
                        break; // if transform has no SpriteRenderer, we leave it out
                    }

                    int currentLayerID = myRenderer.sortingLayerID; //Store SortingLayerID of the current item in the array being accessed
               
                    if (currentLayerID < lowestLayerID) //If the SortingLayerID of the current array item is lower than the previous lowest
                    {
                        lowestLayerID = currentLayerID; //Set the "Previous Value" to the current one since it's lower, as it will become the "Previous Lowest" on the next loop
                        closestItem = i; //Set our topHit with the Array Index value of the current closest Array item, since it currently has the highest/closest SortingLayerID
                        highestSortingOrder = myRenderer.sortingOrder; //Store SortingOrder value of the current closest object, for comparison next loop if we end up going to the "else if"
                    }
                    else if(currentLayerID == lowestLayerID) 
                    {
                        if(myRenderer.sortingOrder > highestSortingOrder) { //If SortingLayerID are the same, then we need to compare SortingOrder. If the SortingOrder is lower than the one stored in the previous loop, then update values
                        closestItem = i;
                        highestSortingOrder = myRenderer.sortingOrder;
                        }
                    }
                }
               
                return hits[closestItem];
            }
2 Likes

Shouldn’t the break in line 14 be a continue?

Breaking out of the for loop would also prevent any further operations on the remaining items in the list. @mcoury is correct, you would use continue to skip that object.

1 Like

Woops. You are right after all, my bad.

1 Like

In the context of event systems, the sprite sorting order factors into raycast ordering. Check out EventSystem.cs