Block physics raycast on gameobjects if UI element was clicked

1.- There is a “Player” gameobject that has a script attached, that in the Update() evaluates if mouse was clicked with Input.GetMouseButtonDown. If true, it does a physics raycast to move or attack the player gameobject to whichever position was clicked on the screen.
2.- There is also a screen canvas that has a panel at the bottom for the skills bar. The panel has 4 images (ie: 4 slots), one for each skill, and each image has a script that implements IPointerClickHandler to detect a click, like in the following video by @BoredMormon


The click on the image is detected Ok. Problem is, that the physics raycast evaluates to true in the player script as well. So when a skill image is clicked, the player also moves/attacks to that position clicked.
Is there a way to “block” the physics raycast done in the player script if an image in the skill bar was clicked? The idea is that if an image in the skill bar was clicked, the physics raycast on the terrain (or enemies, or props in the terrain) is ignored.

The way I can think of (haven’t tried it yet though) is to move out of graphics raycast to physics raycast for the the UI elements as well, adding colliders to the images, and handling all clicks on the screen (either UI or gameobjects) with Input.GetMouseButtonDown on a script attached to the camera.

Thoughts?

I can contribute to this because i’m handling my game a little differently from yours. I’m having a very similar issue. My PlayerController script has methods for left click and right click on the ground/enemy/ally that function using physics.raycast. My UI consists of panels and buttons and images with event triggers. My google searches for the past 2 hours have led me to the conclusion that physics.raycast currently implements no easy solution to hit the UI and thus not hit the objects behind the UI. My desired outcome is the same as yours – I would like the physics.raycast fired when clicking to not hit the objects behind the UI when i’m interacting with the UI. I’m finding a lot of answers how to solve this with every click method except physics.raycast. Seems like something that unity devs will have to add soon.

YOU ARE BOTH IN LUCK!

I just solved this last week!

You are correct, @NatalieBaldwin - this isn’t something that is FULLY supported just yet.

There is a pretty good work around though, and it is a little tedious.

When you first call your click, and send out your raycast - you can do a check, just like anything, for anything you like.

I do certain checks to…

  1. See if the mouse is inside or outside of certain ALWAYS unclickable in-game areas. So this checks for permanent UI interfaces that are always on the screen.
  2. Check to see if any specific UI is open.

Depending on what is returned from those statements, I decide what to do.

My UI handler consists of a whole heap, so I will narrow it down.

		//<-- start of showing tree options.
		if (showing_tree_options == true) {
			GUI.Label (new Rect(Screen.width / 51.3f, Screen.height / 1.17f, Screen.width / 3.0f, Screen.height / 10.0f), "" + selected_tree_object_name, selectedStyleNormal);
			//collected information from the tree (name), and have now displayed it for the player to see.
			//<-- start of chop option button.
			if (GUI.Button (new Rect (Screen.width / 51.3f, Screen.height / 1.099f, Screen.width / 7.0f, Screen.height / 12.72f), "Chop", selectedTreeChopOption)) {
				//the chop button was on display, and has now been clicked.
				player.GetComponent<Player>().setTargetObject(selected_tree_object);
				player.GetComponent<Player>().setTargetLocation(Vector3.zero);
				showing_tree_options = false;
			}
			//<-- end of chop option button.
			//<-- start of cancel option button.
			if (GUI.Button (new Rect (Screen.width / 1.89f, Screen.height / 1.099f, Screen.width / 7.0f, Screen.height / 12.72f), "Cancel", standardButton)) {
				//the cancel button was on display, and has now been clicked.
				showing_tree_options = false;
			}
			//<-- end of cancel option button.
		}
		//<-- end of showing tree options.

In my example of what I would call after I click, before the RayCast, I would for instance check to see if the UI is enabled.

You can get creative, and have one boolean that is set to true if ANY ui is enabled.

Hope this helps.

For touch inputs you can use the following snippet inside Update() as a workaround for this problem, but I think it would also be usable for mouse inputs. Just ensure, that all your UI elements are children of one UI-parent-GameObject.

if(Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began){                              
            
            //check if touch position is over any active and visible UI object
            foreach(MaskableGraphic uiElement in uiParent.GetComponentsInChildren<MaskableGraphic>()){                 if(uiElement.gameObject.activeInHierarchy && uiElement.enabled){ 
                    Vector2 tmpLocalPoint;
                    RectTransformUtility.ScreenPointToLocalPointInRectangle(uiElement.rectTransform, Input.GetTouch(0).position, null, out tmpLocalPoint); 
                    if(uiElement.rectTransform.rect.Contains(tmpLocalPoint)){                        
                        Debug.Log("Raycast blocked by: " + uiElement.name);
                        return;
                    }    
                    
                }    
            }

}

//Start Raycasting
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.GetTouch(0).position);
if(Physics.Raycast(ray, out hit)){
   //Do your stuff here ...
}