Block physics raycast on gameobjects if UI element was clicked
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.
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 https://www.youtube.com/watch?v=EVZiv7DLU6E
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?
Answer by NatalieBaldwin · May 03, 2017 at 02:19 AM
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.
Answer by OfficialCoatsee · May 03, 2017 at 02:52 AM
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...
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.
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.
this wouldn't work for me, because in my case the UI is always enabled. It's an action/skill bar always visible at the bottom of the screen. I thought of verifying if the UI was clicked before the Input.Get$$anonymous$$ouseButtonDown, but the Input.Get$$anonymous$$ouseButtonDown (in the Update() methodof the player script) gets executed before the UI click, so that wouldn't help either. I couldn't find any helpful advice in the forums yet so far, but I'm sure there has to be some solution for this, there are probably many games out there with this same use case.
I think that's exactly what they were saying. If you have UI that is constantly in the same position you can use a few if statements to limit the area of the screen where mouse click fires a raycast. For example, i'm using a side-panning camera scroll. (the camera moves when your mouse is close to the edge of the screen).
if ((Input.mousePosition.x >= Screen.width - mDelta) && (Input.mousePosition.x < Screen.width)) {
With enough of these statements you could carve out an area where it is acceptable to send a raycast. Personally i'm going to save this til later and hope Unity devs fix it up neatly.
Answer by BBIT-SOLUTIONS · Feb 14, 2018 at 03:22 PM
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 ...
}
Your answer
Follow this Question
Related Questions
A canvas to let through a given layer 0 Answers
raycast not hittig anything but charecter 0 Answers
How to offset a RectTransform? 0 Answers
Block some raycasts from world space canvas and ignore others? 0 Answers