How do you set an order for 2D Colliders that overlap?

Using Unity 4.3.1f1 I am making a game where I hide a character behind a door. I want to be able to click the door animate the door and its collider out of the way, and then be able to click on the character when he is visible. I have everything rendering and showing on screen correctly in sorting layers and stuff, but the character gets the click, not the door even though the door draws on top of the character. Obviously transforming the z doesn’t work as that is how I would have solved the problem in 2DTK.
Is there some kind of collision z-index that can be used to set raycast and click preference?
One more piece, the door and character animations and collisions work properly on their own when they are not overlapped.

There seems to be some kind of big with it right now where colliders behind the top one gets the event. It's rather annoying. I'm not sure if there's a work around, but you could also try a google search for unity 2d collider ordering workaround.

Is it me or do these answers feel incredibly long for something simple such as this. If something is placed above another gameobject, it should be clicked first. Why is this so hard for Unity?

7 Answers

7

After a bit of testing, I’ve come to a discovery, which might cast some light on this “problem”.

TL;DR: Go to where it says “For 2D in Unity” in bold text, if you don’t want to learn, but just get the facts.

None of the physics layers, sprite sorting layers or sorting orders seem to have ANY impact on which object a ray hits first. The sprite-stuff only makes a difference to which order the sprites are rendered in, and the physics layers just let you define which objects can collide with each other. It kind of has to be this way, because otherwise these things would be tied together! An objects’ colliders could be on a parent or child object, and there could even be one collider acting on one physics layer only, and another one acting on another layer only. It makes sense that they don’t factor into which object is in front of the other.

Example: Let’s say we have two objects; “Front” and “Back”. They both have a BoxCollider2D and a SpriteRenderer.

I set “Front” to Sprite Sorting Layer “Foreground”, and sprite “Back” to Sprite Sorting Layer “Background”.
Now “Front” is always rendered on top of “Back”, but they both still have the same Z-position, so the ray hits either of them seemingly at random.

Now, if I set the Z-axis of “Front” to be 10 (deeper into the screen), which will make it move BEHIND sprite “Back” in the Scene-view, it will still be rendered AFTER i.e. ON TOP of sprite “Back”, because of the order of the Sprite Sorting Layers, but a screen-ray (see GetRayIntersection() below) will always hit sprite “Back” first, because its Z-position is closer to the camera.

It’s sort of the same with the physics layers. If you used them to control which object is in front of another, then you’d be locked into some irritating situations.

For 2D in Unity:

Physics sorting layers = help you control which types of colliders can hit which types of colliders, and also which of them should ignore raycasts i.e. which of them should be ignored when we do our raycast-clicks (more on them further down).

Sprite sorting layers and sorting orders = control the sprite rendering queue, so the sprites are rendered in the correct order, and makes it possible for you to help Unity batch properly.

Z-position = lets you control which gameobjects are “physically” in front of the others, without affecting sprite rendering order or physics interactions, but affects which 2D collider is considered closest to the screen when casting a ray into the screen as opposed to across the screen.

2D Raycasting = casts a ray through the (x,y) world space coordinate system i.e. across the screen, using given start- and end-coordinates, and returns information about the first collider it hits, if any. If two or more objects have a collider at the exact same coordinates, it will be a luck of the draw. Makes total sense.

GetRayIntersection() = just like raycasting, but can cast a ray “into” the screen of a 2D game, returning information about the first collider it hits.

Solution: Don’t put several objects, that are all supposed to be clickable, on top of each other with the same Z-position. And to find the frontmost collider, DO NOT use Physics2D.Raycast. That is for casting rays in (x,y) space, you know, across the screen.

For hitting the frontmost collider at a screen point, use something like this:

Vector3 worldPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
worldPoint.z = Camera.main.transform.position.z;
Ray ray = new Ray(worldPoint, new Vector3(0, 0, 1));
RaycastHit2D hitInfo = Physics2D.GetRayIntersection(ray);

Code stolen from this stackoverflow post.

It can definitely be shortened to alleviate some instantiation. The “new Vector3(0, 0, 1)” makes the ray shoot directly into the screen, from the clicked position (worldPoint), after worldPoint has had its Z-position changed to that of the camera, instead of the default 0. So it sort of “shoots from the screen” and “into the game”, hitting the first collider it meets. You can go ahead and filter the layers it can hit, by using some of the overloaded versions of Physics2D.GetRayIntersection.

2021 Update:

For getting the frontmost collider where you’ve clicked, you can use Physics2D.OverlapPoint() instead:

Collider2D colliderHit = Physics2D.OverlapPoint(Camera.main.ScreenToWorldPoint(Input.mousePosition));

You can check if colliderHit is null, and if it isn’t, then it is the frontmost thing it hit. You can adjust the Z-range it should search in and which layers to search in, by using the other parameters for the Physics2D.OverlapPoint() function.

For finding all the colliders under the point where you clicked, you can use Physics2D.OverlapPointAll() or Physics2D.OverlapPointNonAlloc() the latter of which is preferred for memory reasons. It requires you to supply an array of sufficient size to put the results in, which you then clear after you’ve used it. Since people click a lot, you don’t want to garbage collect results for every click, thus we use the NonAlloc version and eat the cost of keeping our little array around; it should not need to be very large, anyway. You can see an example in kboudai’s answer below, where results[0] is the frontmost thing hit and any subsequent hits in the array are in Z-order, positive to negative. The actual declaration of the “results” array is missing from kboudai’s answer, but it’d just be something like this in your class:

private Collider2D[] results = new Collider2D[5];

5 is the size of the array, but also the “depth” i.e. the maximum number of collisions we want to detect. OverlapPointNonAlloc simply writes the collisions into the array in order, and if the array is not large enough, it just skips the rest, so we only get the first 5. Then you MUST clear that array (set all its places to null) after you’ve used it. Otherwise, if you detected 5 collisions last time, but only 3 this time, then you’ll have an array with the 3 new ones and the last 2 of the 5 from the last click. It’s annoying, but it saves A LOT of garbage collection!

Thanks for mentioning the Physics layers. I set one of my objects to the IgnoreRayCast layer and now it works just fine.

Thank you for this excellent explanation!

Great explanation and research! I wrestled for a few hours yesterday trying to get a 'tooltip' system working on non-UI gameobjects using the various built-in Unity events, but I'm not 100% happy with the result. So I'm pretty sure I'll need to use raycasts but that'll be a rearchitecting job for a rainy day whilst referring to this excellent answer ;)

I made it work by putting them on the same layer then making their z-position closer to the camera.

No need for 3D.

For example:

GameObject A;
GameObject B;

int Layer = 2;

void Start(){
    A.layer = Layer;
    A.transform.position.z = -1;
    B.layer = Layer;
    B.transform.position.z = 0;
}

void onClick(){
    Vector2 p = Camera.main.ScreenToWorldPoint(pos);
    int hitNum = Physics2D.OverlapPointNonAlloc(p, results, 1<<Layer);
    if(hitNum > 0){
        // Should be A, assuming the camera's z is -10
        Collider2D hit = results[0];
    }
}

Thanks it helps

I think this solves your issue: Not Quite Black and White

I was having the same trouble so I put a script on that blogpost along with an explanation of how it works. Linecast seemed to work better than raycast for me, but I may just have been using it wrong.

Have you found a simpler solution yet (aka has unity fixed this darn bug?)

Very nice script, if you want your sprite sorting layers to determine what's "on top", which is reasonable. Physics2D.GetRayIntersection does the same thing, but only considers the Z-position of the colliders instead of the sorting layers, which in many cases is a better solution, since it's not guaranteed that the top layer is even the one you want (think filters, overlays etc.). With Physics2D.GetRayIntersection, you can use the Z-position of an object, to make that object be "on top", even if that object is not in the top sprite layer. See my answer for details.

I’ve just found out that in such situations you can use 3D colliders, which will support correct z-ordering. You may have problems using 2D and 3D colliders at once, in such case you’ll need to move entirely to 3D colliders.

Is it me or does this seem like a silly issue for Unity 2d to be struggling with? Unity not accounting that some gameobjects must be clicked over others is kind of sad

There is no straight-forward answer to solve this without a complicated work-around.

Personally i would just switch my 2DColliders to 3D ones. The change might be a bit of a hassle but IMO much easier than adding these complicated raycasts to every click event in your game.

You can also my obviously see how a collider interacts within it’s own space. You can still make everything else look 2D with no complication at all.

This works for me; however, I'm using OnMouseOver() instead of raycasting. I assume this would work for that whole set of functions (OnMouseOver, OnMouseDown, OnMouseUp etc.).

Try the example here

http://docs.unity3d.com/Documentation/Components/Layers.html
Read where it says “Casting Rays Selectively”.

I'm not sure the Casting rays selectively section helps in a situation where both items must be clickable at the same time. Is there still no work around for this?

The order of the 2D colliders follow the 3D position of them in space, rather than the sorting order. This is true even if your project is set up for 2D. So what I ended up doing was moving the 2D Colliders closer to my camera when they were supposed to be in front (this did not effect the visual look of the scene because I have an orthographic camera). I agree that this is unintuitive, and that it should probably follow the sorting order.

It should not follow the sorting order. Think how rigid that would be. It's much easier to control rendering order, physics interactions, and mouse-clicks individually. See my answer for an attempt at an explanation.