While testing a new feature in an unannounced game I’m working on, I noticed some confusing behaviour in Unity: if a Screen Space UI element (such as a button) was hovering over a 3D model in the game, clicking on the UI button would work as expected, but the model under the button was also reacting as though it had been clicked on. This was obviously not the behaviour I wanted.
I went searching for a solution to this problem, and I couldn’t find a simple explanation of how to solve it anywhere, so once I figured out how to solve the problem I figured I should write out what I’ve learned so that anyone else encountering the same problem could save some time. This code was tested in Unity 2021.3, but should work in other versions.
Physics Raycasts
First, let’s set the scene. A fairly standard task that you’ll do in Unity is raycast into the scene to see if the user’s mouse is hovering over any game elements. For example, if you’re creating a game like Diablo you could have items on the ground that the player can click on to add to their inventory. Your code to check for these clicks might look something like this:
public LayerMask layerMask;
private void CheckForClicks()
{
// Check for mouse clicks. "Select" is linked to a left mouse click in the input manager.
if (Input.GetButtonDown("Select"))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, layerMask))
{
// Do something with the object that was hit by the ray
}
}
}
However, as I discovered, if you have a Screen Space UI element placed over the objects on the LayerMask, this code will still receive mouse clicks on those objects. This is because the UI isn’t receiving raycasts from the camera. But you probably want the UI to intercept these clicks, so that the objects underneath don’t activate when clicking on the UI. To do that, you need to add a different type of raycast, a graphic raycast.
Graphic Raycasts
Your UI canvas should have a Graphic Raycaster component attached to it by default, and you can use this raycaster to check if the player is hovering over a UI element. Let’s update the code from the previous example to do that. We’ll need to add references to the Graphic Raycaster and the Event System.
public LayerMask layerMask;
public GraphicRaycaster graphicRaycaster;
public EventSystem eventSystem;
private void CheckForClicks()
{
// Check for mouse clicks. "Select" is linked to a left mouse click in the input manager.
if (Input.GetButtonDown("Select"))
{
// Check if there are any UI elements underneath the current mouse position
PointerEventData pointerEventData = new PointerEventData(eventSystem);
pointerEventData.position = Input.mousePosition;
List<RaycastResult> results = new List<RaycastResult>();
graphicRaycaster.Raycast(pointerEventData, results);
// "results" stores a list of all the objects hit by the graphic raycast. If the number of results is zero, you know you're in the clear and there are no UI elements underneath the mouse.
if (results.Count == 0)
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, layerMask))
{
// Do something with the object that was hit by the ray
}
}
}
}
And that’s it. All you have to do is use a graphic raycast to check for UI elements, and then proceed with a physics raycast as normal. Hopefully this saves you some time if you’ve run into the same problem I did.
REFERENCES: