Preventing clicks from going through UI elements in Unity
In 20 lines of code 🌟
UI systems in Unity
Unity has three different packages for building user interfaces (UI): Unity UI (a.k.a. uGUI), IMGUI and UI Toolkit and all of them are used for different things:
IMGUI focuses on building small in-game debugging displays or for extending the Unity Editor itself.
uGUI is the default
GameObject
-based UI system and is the most widely used.Finally, UI Toolkit is the newest package and should replace uGUI in the near future. If you are familiar with designing web interfaces (i.e. using HTML/CSS), UI Toolkit is supposed to be easier to learn as it is heavily inspired by web technologies.
While UI Toolkit is intended to become the recommended UI system for new development projects, it is not yet at feature parity with uGUI and, as such, Unity still advises using uGUI for the time being. You can check out the Comparison of UI systems in Unity on Unity docs for more information.
In this blog post, we exclusively talk about uGUI. The issue may also happen with UI Toolkit though, but I can't confirm.
The problem
Unity is notoriously bad for designing user interfaces. Just a quick search on Google can confirm this. Looking at these twoposts on Reddit, it really shows how much hate uGUI is getting! 😤 And for valid reasons: at first, the UI system is not very intuitive. It's hard to design reusable components, and it requires lots of Unity know-hows.
Also, uGUI seems to make it hard to implement basic features that anyone would expect in any UI builder. A good example of this is capturing clicks on UI elements (e.g. buttons, dropdowns, sliders, etc…). By default, meaning if you don't write custom C# code, clicking on UI elements within a scene will trigger events that will be consumed by both the UI and the game. As stated in the docs:
UI in Unity consumes input through the same mechanisms as game/player code. Right now, there is no mechanism that implicitly ensures that if a certain input – such as a click – is consumed by the UI, it is not also "consumed" by the game.
Yes, it's indeed pretty disappointing that such a seemingly simple use case is not taken care of by the engine itself.
A workaround exists for the now deprecated InputManager via EventSystem.IsPointerOverGameObject
to check if the mouse is above a UI element (“GameObject” here is referring to UI elements only). Unfortunately with the new InputSystem, while this technically still works, it is seen as a bad practice and Unity will log a warning every time you call this method within the InputAction
callback. See Unity documentation:
Calling
EventSystem.IsPointerOverGameObject
from withinInputAction
callbacks such asInputAction.performed
will lead to a warning. The UI updates separately after input processing and UI state thus corresponds to that of the last frame/update while input is being processed.
Basically, the previous workaround can't be used anymore and Unity didn't expose another method to achieve the same result. If you read Unity's documentation carefully, they give some pointers, but we can pretty much summarize them by saying “please, don't design your game so that both game and UI logic are needed at the same time”…
So, what can we do ? 🤔
The solution
What we can do is reproduce what EventSystem.IsPointerOverGameObject
does, minus the warning. It's actually very simple and requires less than 20 lines of code: drum-rooooolllll
Graphic raycaster
A GraphicRaycaster
is an important part of uGUI and allows us to cast rays from the camera to any point on a canvas and get the list of game objects that have been hit.
Also, if you ever created a Unity Canvas before, you already used a GraphicRaycaster without even knowing it. They are attached to any Canvas when you create one from the Unity Editor (right-click > UI > Canvas):
To cast a ray, we use the GraphicRaycaster.Raycast(PointerEventData, List<RaycastResult>)
where:
PointerEventData
represents the point from which to raycast, usually the position of the mouseList<RaycastResult>
is used to store references to the game objects that have been hit
Putting it all together in a simple C# script as follows (the important part is the `HasClickedOverUI` method 😉):
public class PlayerInputController : MonoBehaviour
{
// The GraphicRaycaster of your Canvas game object
[SerializeField]
private GraphicRaycaster graphicRaycaster;
// Struct to hold pointer data (mainly its position)
private PointerEventData _clickData;
// List containing all the UI elements hit by the raycast
private List<RaycastResult> _raycastResults;
private void Start()
{
_clickData = new PointerEventData(EventSystem.current);
_raycastResults = new List<RaycastResult>();
}
public void OnClick(InputAction.CallbackContext context)
{
if (!context.performed)
{
return;
}
// Check that user did not click over a UI element
if (HasClickedOverUI())
{
return;
}
// Game logic continues here...
// For example, call other C# methods, play a sound, instantiate
// a game object, etc...
}
private bool HasClickedOverUI()
{
// Retrieve current mouse position
_clickData.position = Mouse.current.position.ReadValue();
// Clear previous results
_raycastResults.Clear();
// Instruct the raycaster to cast a ray from current mouse
// and stores the results in the given array
graphicRaycaster.Raycast(_clickData, _raycastResults);
// Optional: log all the UI elements hit by the ray
foreach (var raycastResult in _raycastResults)
{
Debug.Log($"Clicked in UI element: ${raycastResult}");
}
// Return a boolean that will tell us whether at least one
// UI element has been clicked on
return _raycastResults.Count > 0;
}
}
Take a look at the OnClick
method which calls HasClickedOverUI
before executing any game logic. This will effectively disable any processing in case the click was made wile over a UI element.
It's that simple! 🔥 However, one could argue that it actually does not prevent clicks to go through UI elements. It simply enables game objects to ignore clicks if they have been made over UI elements (potato, potahto).
Final words
While uGUI, Unity default UI system, is powerful and can be used to build even the most complex user interfaces, it definitely has a steep learning curve. Also, like we saw, some of the most basic tasks may require custom C# code.
Hopefully, the newest alternative, UI Toolkit, will simplify all of that and will make it easy to design and interact with in-game UIs. If not, I have my eyes on another solution that could benefit the whole game development community: EvolveUI (currently in closed-beta, March 2024). I might even write another article if I use it, who knows ? 😇