World Canvas Raycasting not blocked by other game objects?
I am using several world canvas instances in my scene
and it seems like the raycast order is not working as it should.
An easy setup to show the problem can look like the following:
Place a UI canvas in the scene,
change "Render Mode" to "World Space",
set position and scale accordingly so that it has the size of other game objects like a cube.
Add a simple button to the canvas and
place a cube infront and behind the canvas.
Now change the "Graphic Raycaster" component settings to:
- Ignore Reversed Graphics: false
- Blocking Objects: "All"
- Blocking Mask: "Everything"
Here an image of the setup:
And here another image showing the problem I'm facing:
As you can see, even tho the ray should be blocked by the cube,
the event is still forwarded to the canvas/button element behind.
When you try the same from "behind" the canvas, it does not show this behaviour.
In such a case, the cube does (as desired) block the ray.
So is there any way to fix this behaviour so that objects infront always block the ray?
It is required for my application to work like this because otherwise you easily interact with a canvas that is somewhere behind and you actually should not.
Thanks for any helpful ideas.
A fix that worked for me:
This is only useful if you have access to the code that generates the ray itself.
I mainly use it for a VR pointer.
Instead of using the FindFirstRaycast() method, I wrote and used the following code:
bool minSet = false;
float minDistance = 0;
RaycastResult result = new RaycastResult();
foreach (RaycastResult r in m_RaycastResultCache) {
if (r.gameObject == null) { continue; }
// rounding is applied because otherwise the min object switches many times
// (e.g. if a button with a text is hovered over) - which can lead to weird behaviour.
// rounding at 2 decimals after comma is enough precision for now
float dist = Mathf.Round(r.distance * 100) / 100f;
if (!minSet || dist < minDistance) {
minSet = true;
minDistance = dist;
result = r;
}
}
The hit precision is pretty inaccurate after the first two decimals after comma.
That's why I simply round the distance accordingly.
This should mainly be a problem if you have direct feedback (e.g. like a vibration of the controller).
Maybe tell me if this was useful for you
or if you have ideas for improvements.
Answer by S1R0 · Jan 12, 2019 at 02:02 PM
Update 1:
I also tested this in a "fresh" scene - without any additional scripts attached.
This only seems to happen in one direction (from negative to positive Z-axis)
and if a collider is behind the Canvas (in my case there is the ground Plane).
Removing/Disabling the ground plane makes the bug disappear but is no solution.
Any ideas?
Update 2:
Okay, another idea why this could happen:
The raycasting used by the camera obviously seems to use the event system.
There is the method EventSystem.RaycastAll which I think the camera uses.
One of the BaseRaycasters it uses is the PhysicsRaycaster and looking up the Physics.RaycastAll method for it, it states that:
Casts a ray through the Scene and returns all hits. Note that order is not guaranteed.
So thats probably the reason why it only works in one direction.
Used to retrieve the result, is probably the method FindFirstRaycast.
There is no information on how this method is implemented.
So if you have access to the source code, please let me know.
I assume it just goes through the list of raycast results and checks on after another until it finds the first valid raycast result.
In this case (because the order is not guaranteed) it doesn't have to be the first that was hit, or the first thats the closest to the origin.
So I probably have to replace this check my own, which I hope will work and don't affect performance too much.
Maybe one of you can help me out with something I don't know that already does this?
Update 3:
Okay, my guess was correct that the "FindFirstRaycast" just takes the first result of the list that is not null.
I wonder why this method then even exists then...
Here is the part of the source code:
protected static RaycastResult FindFirstRaycast(List<RaycastResult> candidates) {
for (var i = 0; i < candidates.Count; ++i)
{
if (candidates[i].gameObject == null)
continue;
return candidates[i];
}
return new RaycastResult();
}
So the thing I consider to do now is replacing the use of this method in my application
by something like the following:
"Find the GameObject with the smallest distance (hit distance) thats not null and return it."
I know that this check takes a bit more time but I guess it wont be as much as long as there are not always 10000+ entries in the list.
I gonna post an update soon on how it worked out and if it solved my problem.
Answer by Alex_Heizenrader · Sep 18, 2019 at 04:42 PM
Thanks for this @S1R0 , I was puzzled with the same setup you have there and a VR laser pointer we wrote haha