- Home /
Raycast against UI in world space
How do I cast a ray from the mouse position to the world and check if I hit a UI element in world space
I need to detect which UI element was clicked and adding an OnClick() event to each element is NOT and option.
Some type of raycast seems to be my best option but I have no Idea where to start.
I've already tried EventSystem.current.RaycastAll() but it does not give information on clicks
Thanks
Answer by MarkTerence · Jun 14, 2016 at 08:35 AM
Here is a quick way, so you don't need to attach a script on every world UI elements you have. - Add a new Layer, you can name it something like 'World UI' -On your World Canvas (the canvas having a world space render type), set its Layer to 'World UI' or the layer you just created. - attach this script on the world canvas or at any game object, empty or not
using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using System.Collections.Generic;
public class UIWorldDetect : MonoBehaviour {
void Update () {
RaycastWorldUI();
}
void RaycastWorldUI(){
if(Input.GetMouseButtonDown(0)){
PointerEventData pointerData = new PointerEventData(EventSystem.current);
pointerData.position = Input.mousePosition;
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
if (results.Count > 0) {
//WorldUI is my layer name
if (results[0].gameObject.layer == LayerMask.NameToLayer("WorldUI")){
string dbg = "Root Element: {0} \n GrandChild Element: {1}";
Debug.Log(string.Format(dbg, results[results.Count-1].gameObject.name,results[0].gameObject.name));
//Debug.Log("Root Element: "+results[results.Count-1].gameObject.name);
//Debug.Log("GrandChild Element: "+results[0].gameObject.name);
results.Clear();
}
}
}
}
}
Ok, so this works really well but is there a global pointerData I can use ins$$anonymous$$d of me having to create one manually. It seems like that would make more sense?
hmm. im not sure of how to make the PointerEventData global.
I updated the UIWorldDetect class a little, so i can call a function to tell whether if a UI element was clicked.
using System.Collections;
using UnityEngine.EventSystems;
using System.Collections.Generic;
public class UIWorldDetect : $$anonymous$$onoBehaviour {
private static UIWorldDetect _instance;
public static UIWorldDetect Instance{
get{
if(_instance==null){
_instance = GameObject.FindObjectOfType<UIWorldDetect>();
}
return _instance;
}
}
private PointerEventData pointerData;
private GameObject RootElementUI;
//returns true if a ui elemend was clicked
public bool isUIElementRaycasted(string gameObjectName){
if(RootElementUI == null)
return false;
else{
if(gameObjectName==RootElementUI.name){
RootElementUI = null;
return true;
}
else return false;
}
}
void Update () {
RaycastWorldUI();
}
void RaycastWorldUI(){
if(Input.Get$$anonymous$$ouseButtonDown(0)){
pointerData = new PointerEventData(EventSystem.current);
pointerData.position = Input.mousePosition;
List<RaycastResult> results = new List<RaycastResult>();
EventSystem.current.RaycastAll(pointerData, results);
if (results.Count > 0) {
if (results[0].gameObject.layer == Layer$$anonymous$$ask.NameToLayer("WorldUI_1")){
string dbg = "Root Element: {0} \n GrandChild Element: {1}";
RootElementUI = results[results.Count-1].gameObject;
Debug.Log(string.Format(dbg, results[results.Count-1].gameObject.name,results[0].gameObject.name));
//Debug.Log("Root Element: "+results[results.Count-1].gameObject.name);
//Debug.Log("GrandChild Element: "+results[0].gameObject.name);
results.Clear();
}
}
}
}
}
and tried it
public class TestUIWorldHandler : $$anonymous$$onoBehaviour{
void Update () {
//isUIElementRaycasted("GameObjectName must be unique")
if(UIWorldDetect.Instance.isUIElementRaycasted("Button")){
Debug.Log("Press");
}
}
}
regarding on a global PointerEventData i found a rather old thread about it,
Answer by Brijs · Jun 14, 2016 at 07:13 AM
To use ray cast you should have collider on object.
Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
This is how you can get ray from mouse position
Now for click detection on world space UI, make sure that the "GraphicsRaycaster" is attached to canvas.
Implement "IPointerClickHandler" interface and write code to interface method.
public class NewBehaviourScript : MonoBehaviour,IPointerClickHandler
{
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log("Clicked" + gameObject.name);
}
}
Attach this monobehaviour to UI element
If you want to use physics raycast to detect click on UI element then you should attach collider to ui element
Best answer this one. People who post loads of code Like $$anonymous$$ark Terrence above, really only make it more difficult, for people grasping the basics, trying to sift through it all.
Answer by ryo0ka · Apr 24, 2019 at 05:57 AM
Mark's answer above didn't work for me. I'm developing an AR/VR game and my primary use of UI raycasting is to detect whether the user (camera) is facing the canvas at a given frame.
I looked up the Unity UI repository and stripped out the part that's doing the raycasting:
void Update()
{
IsFocused = false; // Member property... change this to whatever you want
Vector2 center = _camera.ViewportToScreenPoint(Vector2.one / 2); // Center of camera
var graphics = GraphicRegistry.GetGraphicsForCanvas(_canvas); // All Graphics in canvas
for (var i = 0; i < graphics.Count; i++)
{
var t = graphics[i].rectTransform; // Get the Graphic's RectTransform
Debug.Log($"name: {t.name}"); // Show their names if you want
// Check if the RectTransform "contains" the center of camera == is "looked at" now
IsFocused |= RectTransformUtility.RectangleContainsScreenPoint(t, center, _camera);
}
//Debug.Log(IsFocused); // Should be true if any Graphics were "looked at"
}
...which works for me. IsFocused
in the code above will be true if the camera is looking at one or more Graphic components in the canvas (i.e. Image
, RawImage
, Text
, etc...)
The key APIs in this code are
GraphicRegistry.GetGraphicsForCanvas()
andRectTransformUtility.RectangleContainsScreenPoint()
.
By using these functions properly you can check if your canvas "contains" any screen-space points in your camera. You can also apply these functions to the canvas itself's RectTransform for better performance if your UI components will sit exactly within the canvas.
Note that this code does not take account of objects occluding the canvas from the camera -- the code wil always say "true" even if the canvas may be practically invisible from user. You'll need to revisit the Unity UI repository above and see how the official API (is trying to) solve the situation if you need to test such occlusions.
Hoping that this will help someone else.
Here's the new link if anyone wondering.
https://github.com/Unity-Technologies/uGUI/blob/2017.1/UnityEngine.UI/UI/Core/GraphicRaycaster.cs
Your answer
Follow this Question
Related Questions
UI - How can I prevent a Rect Transform on a canvas from blocking clicks? 1 Answer
4.6 UI "image" is capturing clicks - how to prevent? 3 Answers
How to wait click on button in my MessageBox analogue? 1 Answer
Create UI raycast like a mouse Click 2 Answers
How to make UI block clicks from triggering things behind it 7 Answers