How can I detect if UI elements are overlapping without colliders? Rect.Overlap is not working.

Hello,

I want to detect if two or more UI elements are overlapping each other. I tried this with box colliders2D and rigidbody2D but on runtime when I access my menu I set the timescale to 0, so colliders didn’t work, realized after I finished all my work :'(.

I also tried Rect.Overlap() and for test purpose I set a variable to true if two rect are overlapping, but I always get true back and never false.

Then I found this: 2D collision detection - Game development | MDN and tried to make it work, but same result as the Rect.Overlap(). Here is my code:

public class Rect_Test : MonoBehaviour
{
     public GameObject image2;
     Rect image1rect;
     Rect image2rect;
     public bool col;
    
     // Start is called before the first frame update
     void Start()
     {
         image1rect = transform.GetComponent<RectTransform>().rect;
         image2rect = image2.transform.GetComponent<RectTransform>().rect;
     }
     // Update is called once per frame
     void Update()
     {
         if (image1rect.x < image2rect.x + image2rect.width &&
             image1rect.x + image1rect.width > image2rect.x &&
             image1rect.y < image2rect.y + image2rect.height &&
             image1rect.y + image1rect.height > image2rect.y)
         {
             col = true;
         }
         else
         {
             col = false;
         }
     }
}

Can someone explain me what I did wrong? Is this the best solution to detect “collision” of UI or should I use Raycast for it? My Canvas is set to Render Mode: Screen Space - Overlay and UI Scale Mode: Scale with Screen Size if that matters.

You should really take a look at the actual rect coordinates you are comparing, and see whether they’re what you expected.

First, let me point out that you are testing the coordinates of your rectangles every frame in Update, but you are only getting those coordinates in Start. A Rect is just a collection of numbers; it won’t update if the object moves later on. So at best you are only testing whether they overlapped at Start() time, not at any other time. If you want to check based on the current positions, you should only cache references to the RectTransforms (not the Rects), and then get their current rects every time you want to do the check.

I suspect you’ve also got a problem with different coordinate spaces. RectTransform.rect gives you coordinates in the local space of the transform. That’s probably fine if the transforms you are comparing are siblings in the object hierarchy, but otherwise you probably need to convert everything to global coordinates using something like TransformPoint. (I don’t think there’s an existing function to transform the entire rect at once, so you’d need to either convert opposite corners, or convert one corner and then use Transform Vector to convert the dimensions.)

(And even that might not do what you want unless the objects are at least in the same canvas–if you have worldspace canvases at different locations, then the problem isn’t strictly 2D anymore.)

Yes, as Anti points out, the rects are local to their parents, as per the docs.

There is this handy utility as well though:

https://docs.unity3d.com/ScriptReference/RectTransformUtility.html

@Antistone I still think your avatar graphic is a Space Shuttle every time I see it, like for a fraction of a second.

It’s actually the Holy Ground card image from my least-successful board game.

2 Likes

Thank you Antistone and Kurt-Dekker for your quick answers.
I got it working by now by taking also the RectTransform of the images and use their localPosition in the formula.
That should work for what I was planning. Now I will implement this for different shapes of objects.

public class Rect_Test : MonoBehaviour
{
    public GameObject image2;
    Rect image1rect;
    Rect image2rect;
    public bool col;
    RectTransform image1rt;
    RectTransform image2rt;
   
    // Start is called before the first frame update
    void Start()
    {
        image1rect = transform.GetComponent<RectTransform>().rect;
        image2rect = image2.transform.GetComponent<RectTransform>().rect;

        image1rt = transform.GetComponent<RectTransform>();
        image2rt = image2.transform.GetComponent<RectTransform>();
    }

    // Update is called once per frame
    void Update()
    {
        if (image1rt.localPosition.x < image2rt.localPosition.x + image2rect.width &&
            image1rt.localPosition.x + image1rect.width > image2rt.localPosition.x &&
            image1rt.localPosition.y < image2rt.localPosition.y + image2rect.height &&
            image1rt.localPosition.y + image1rect.height > image2rt.localPosition.y)
        {
            col = true;
        }
        else
        {
            col = false;
        }
    }
}
1 Like