How do I prevent UI click from passing through to the gameworld?
In my game, the user can click on the world and build new buildings. This is handled in my own script “GameUI.cs”.
There are also a few UI buttons, but when those are pressed, it also invokes the GameUI and interacts with the world. How do I prevent this?
I think the older Input system had something like “IsPointerOverGameObject”.
A couple of things to keep in mind. Basically, the “IsPointerOverGameObject” SHOULD be the thing to use, even with the new input system. At least until they provide a new API (system), for checking that kind of stuff. But I’ve ran into this issue as well.
The first thing to check is IF the camera in your scene has a “Physics 2D Raycaster” component. From what I understand, this is what allows you to check for UI events (hover, click, etc) from Collider2D components; which normally don’t contribute to UI events firing. So the “Physics 2D Raycaster” is what bridges the gap in being able to click on 2D Colliders.
Also, there should be a dropdown on “Physics 2D Raycaster” called “Event Mask”, which basically limits the UI event checking to only colliders in that layer, from what I understand. I don’t know if there are any other things that limit which 2d colliders are not “clickable”. But all I have done is set the “Event Mask” to ONLY be enabled for the built-in “UI” layer that you will see in the dropdown. And you just want to make sure that all your clickable colliders are using the UI layer.
I will also show my code here that I use, but I know for a fact that there is a better way, I have just been using this because It’s not giving me any issues. But because I am creating a new List every time, it is creating a lot of garbage, which can be improved.
PointerEventData ped = new PointerEventData(EventSystem.current);
ped.position = Input.mousePosition;
// this is some nasty garbage collection going on here!
List<RaycastResult> mouseUIResults = new List<RaycastResult>();
EventSystem.current.RaycastAll(ped, mouseUIResults);
bool mouseOverElement = false;
foreach(var result in mouseUIResults) {
if(LayerMask.LayerToName(result.gameObject.layer) == "UI") {
mouseOverElement = true;
break;
}
}
But I suggest trying the IsPointerOverGameObject function, or this version of it: EventSystem.current.currentInputModule.IsPointerOverGameObject(UnityEngine.InputSystem.Mouse.current.deviceId);
And see if it works.
EDIT: If you’re making a 3D game, then you would use the “Physics Raycaster” I believe.
Also, there is the “Graphics Raycaster”, which is normally attached to the Canvas root object. It has some settings I have never touched before, but understanding what they do might give you some ideas of how you can customize things for your game.
Note that there’s some gotchas around IsPointerOverGameObject. There’s some new bits added to the docs in 1.1-pre.6 and a new “UI vs Game Input” sample that has been added to the package.
Was added in 1.1-pre.6. 1.1 final is out now so should pop up after upgrading.
Calling IsPointerOverGameObject() once from Update() actually is a lot more performant than doing the equivalent from action callbacks as pointer positions may change many times in a frame. An IsPointerOverGameObject() call from within Update() is pretty cheap as it only requires polling some state that has been put in place by UI processing for the frame.