Button not blocking the raycast

Hi,

I want to be able to place things over object inside the game (like a menu) and when i click inside that object it does not select/trigger the objects hidden by

Here is the scene setup:

  1. a canvas with a single button
  2. a gameObject with a box collider position partially under the button
  3. a script that handles the mouse click and then detects if the mouse was over an object with a collider

My problem is that clicking inside the button also is triggering the script to tell if the object was clicked on.

public class ClickManager : MonoBehaviour {

void Update () {
    if (Input.GetMouseButtonDown(0)) {
        Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        Vector2 mousePos2D = new Vector2(mousePos.x, mousePos.y);
        
        RaycastHit2D hit = Physics2D.Raycast(mousePos2D, Vector2.zero);
        if (hit.collider != null) {
            Debug.Log(hit.collider.gameObject.name);
        }
    }
}

Can anyone point me in the right direction?

Thanks in advance.

The solution I have for you implies that you change the way clicks are detected (meaning removing the code you use and using the one I suggest instead). If you and your code are open to that change, you can create a simple component using the IPointerDownHandler interface. This will force the use of a public void OnPointerDown(PointerEventData e) method in your component, which will then be called at runtime whenever a click is detected.

using UnityEngine;
using UnityEngine.EventSystems; // <-- Important line.

public class Clickable : MonoBehaviour, IPointerDownHandler // <-- Interface.
{
    public void OnPointerDown(PointerEventData data) // <-- Automatically called.
    {
        Debug.LogFormat("Click detected at {0}.", data.position);
    }
}

You will need a PhysicsRaycaster or Physics2DRaycaster on your camera gameobject, depending on if you use 2d physics or 3d physics. No raycast calculation needed, it’s all done for you. You will also need an EventSystem component in your scene, but this one is added automatically when you add a Canvas.

I tried this solution myself and it worked: the OnPointerDown method was not called when I pressed a button that was over the sprite having my custom Clickable component.

For more information on all the interfaces you can use, [click here][1] and use the navigation menu on the left.

[EDIT]

Here is a second possibility, one using your custom detector (and tweaking it a bit). It’s a bit hacky and needs refactoring in my opinon. I will leave you the pleasure of doing that if you use it. Even then, it will still be hacky and it will be a hassle to maintain.

You will first need to place and manually adjust colliders on UI elements that should block clicks.

Then, you can use RaycastAll instead of Raycast to detect every game object in the way. By checking if a game object’s layer corresponds to the UI layer, you can determine if any UI is in the way. Here is the updates code:

using UnityEngine;

public class ClickDetector : MonoBehaviour
{
    int uiLayer;

    void Start()
    {
        // taking note of UI layer's id for posterity.
        uiLayer = LayerMask.NameToLayer("UI");
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            Vector2 mousePos2D = new Vector2(mousePos.x, mousePos.y);

            // Raycast all the things.
            RaycastHit2D[] hits = Physics2D.RaycastAll(mousePos2D, Vector2.zero);
            GameObject hitObject = null;

            for (int i = 0; i < hits.Length; i++)
            {
                if (hits*.transform.gameObject.layer == uiLayer)*

{
// We get out of here if UI is detected.
return;
}

if (hitObject == null && hits*.collider != null)*
{
// We take note, but do nothing yet.
// There may still be some UI laying around…
hitObject = hits*.collider.gameObject;*
}
}

Debug.Log(hitObject.name);
}
}
}
Why I prefer the first solution
Less code, obvious bugs, less hassle.
The first solution will do nothing if something (event system, physics raycaster, custom Clickable component) is missing. It will outright bug and you will see it immediatly. Also, a friend once told me: “The best code is the code that doesn’t exist.” Less code is needed for the first solution. Is is clearly the best to me since it only needs an initial setup in the scene and every UI element will block raycasts until the end of time.
Why I don’t like the second solution
More code, subtle bugs, more hassle.
The second solution will have hidden bugs if something (UI layer, collider) is missing. Meaning everything will work nicely unless the bug’s conditions are met: clicking a button on top of a clickable sprite. It also needs to go through the trouble of putting and adjusting colliders on your UI. I haven’t fully tested collisions, but I know you can check the IsTrigger box and your button will still block inputs to world objects.
_*[1]: Redirect to... title of new-page

Thanks!! its work for me,

The problem is that the physics raycaster on your camera is still active while your menu is shown.

Steps:

  1. When showing the UI (eg “menu”) disable the physics raycaster on the camera
  2. Show the UI (canvasses use their own graphics raycaster)
  3. When hiding the UI (eg the “menu”) re-enable the physics raycaster on your camera

Hope this helps

With kind regards,

Koen Matthijs