Unity-Technologies / arfoundation-samples

Example content for Unity projects based on AR Foundation
Other
3.03k stars 1.13k forks source link

Block ARSessionOrigin.Raycast with UI #25

Closed southrop closed 4 years ago

southrop commented 6 years ago

Is it possible to block the raycast from ARSessionOrigin in PlaceOnPlane.cs with a UI component? It appears as though the raycast ignores all non-Trackable objects at the moment.

I feel that having a UI element overlaid over an AR view is not an uncommon usecase, and surely people don't want the AR scene reacting to taps when all they wanted to do is press a button.

bitfabrikken commented 6 years ago

I agree, I would definitely need this as well.

Saicopate commented 6 years ago

Valid issue, agree.

ghost commented 6 years ago

PlaceOnPlane is a very bare bones example of how to do a raycast. You can adjust the logic to only do this if UI elements are interacted with first. I think the longer term solution would be to have a custom InputModule for ARFoundation. I've added it to our backlog.

southrop commented 6 years ago

@timm-unity Thanks for the explanation. I realised that Physics.Raycast actually ignores UI components to begin with, so I might have been misunderstanding the raycast behaviour. Do you have any suggestions as to how best to adjust this logic to ignore the touch if it hits a UI component first? Would the best thing to do be to add a script to the ARPlane that subscribes to touch events instead of using a script on ARSessionOrigin, or is that unreliable somehow?

edit: Managed to get it working using the method I mentioned above. Hopefully this is the "right"/"recommended" way to do it haha... Or maybe not...

kbegiedza commented 6 years ago

@southrop Check if touch was above any GUI element with GraphicsRaycaster first.

southrop commented 6 years ago

@Ursanon Thanks for the suggestion. Correct me if I'm wrong, but GraphicRaycaster is specific to the canvas it is attached to. Unfortunately, my game uses multiple canvases to handle pop up dialogs and such additional UI elements (I didn't make the system), so it seems like an expensive operation to have to Find all the canvases and then raycast for each one of them.

bitfabrikken commented 6 years ago

@southrop I ended up doing normal raycasting against layers and it works very well. It also respects UI elements such as raycast blocking etc., if you include it in the layermask you cast against. I've got the ARPlanes in its own layer, and AR-placed objects in another layer. So it's easy to cast again specific things by using the layermasks.

Pseudocode:

void CheckIfStartDrag(){
    RaycastHit hit; 
    Ray ray = Camera.main.ScreenPointToRay(finger.ScreenPosition); 
    if (Physics.Raycast(ray, out hit, rayDistance, 1 << layerMaskUI)) {
        Debug.Log("-- hit was ui, not starting drag"); //this will just allow the UI system to handle the click/touch/whatever
        return;
    }
    if (!Physics.Raycast(ray, out hit, rayDistance, layerMaskDragGroup)) {
        Debug.Log("hit nothing draggable, hit layer: "+hit.transform.gameObject.layer);
        return;
    }
}
kbegiedza commented 6 years ago

@bitfabrikken Do you have UI with colliders? @southrop Yeah, that's quite expensive, you can try caching GraphicRaycasters or even better - use: EventSystem.IsPointerOverGameObject.

bitfabrikken commented 6 years ago

@Ursanon huh? No.

southrop commented 6 years ago

@bitfabrikken Interesting. Physics.Raycast isn't supposed to work on UI because they don't have colliders, which is why GraphicRaycaster exists.

@Ursanon I'm actually already using EventSystem.current.IsPointerOverGameObject in my code as I found that via Googling around this issue, but my experience has been that it returns false all the time for some reason. As a quick test, I tried sticking it into the Update() function in PlaceOnPlane.cs in this sample project to make it just return without doing anything if it is true, but no effect even when tapping above a UI button. I can't honestly say I'm too familiar with EventSystem as a whole, so I might be doing something wrong...

kronholm commented 6 years ago

@Ursanon @southrop Sorry got confused there a moment. I do have "Raycast target" checked on interactable stuff on my UI, like buttons with images. (replied from wrong account)

digitalmkt commented 4 years ago

After trying all solutions I found this piece of code works fine for me on Unity 2019.2.15f1 and AR Foundation 3.0.0

bool IsPointOverUIObject(Vector2 pos)
{
    if (EventSystem.current.IsPointerOverGameObject())
        return false;

    PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
    eventDataCurrentPosition.position = new Vector2(pos.x, pos.y);
    List<RaycastResult> results = new List<RaycastResult>();
    EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
    return results.Count > 0;

}

And the Add the following in every touch call:

    if (!IsPointOverUIObject(touchPosition) && m_RaycastManager.Raycast(touchPosition, s_Hits, TrackableType.Planes))

I hope it helps even after the thread closed.

lassehh92 commented 4 years ago

After trying all solutions I found this piece of code works fine for me on Unity 2019.2.15f1 and AR Foundation 3.0.0

bool IsPointOverUIObject(Vector2 pos)
{
    if (EventSystem.current.IsPointerOverGameObject())
        return false;

    PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
    eventDataCurrentPosition.position = new Vector2(pos.x, pos.y);
    List<RaycastResult> results = new List<RaycastResult>();
    EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
    return results.Count > 0;

}

And the Add the following in every touch call:

    if (!IsPointOverUIObject(touchPosition) && m_RaycastManager.Raycast(touchPosition, s_Hits, TrackableType.Planes))

I hope it helps even after the thread closed.

This worked for me! Thanks :)

visualizor commented 4 years ago

@digitalmkt The most straightforward, working solution I've found. Thanks! FYI I'm on 2019.1.0f2 abd ARFoundation 2.2.0

majid-ibrahim15 commented 3 years ago

After trying all solutions I found this piece of code works fine for me on Unity 2019.2.15f1 and AR Foundation 3.0.0

bool IsPointOverUIObject(Vector2 pos)
{
    if (EventSystem.current.IsPointerOverGameObject())
        return false;

    PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
    eventDataCurrentPosition.position = new Vector2(pos.x, pos.y);
    List<RaycastResult> results = new List<RaycastResult>();
    EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
    return results.Count > 0;

}

And the Add the following in every touch call:

    if (!IsPointOverUIObject(touchPosition) && m_RaycastManager.Raycast(touchPosition, s_Hits, TrackableType.Planes))

I hope it helps even after the thread closed.

Worked for me as well...Thanks :)

marcusx2 commented 3 years ago

It only works if I remove the

if (EventSystem.current.IsPointerOverGameObject())
        return false;

bit. After all UI elements are game objects as well. Why does this snippet exist?

marcusx2 commented 3 years ago
bool IsPointOverAnyObject(Vector2 pos)
        {
            PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current);
            eventDataCurrentPosition.position = new Vector2(pos.x, pos.y);
            List<RaycastResult> results = new List<RaycastResult>();
            EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
            return results.Count > 0 || Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out RaycastHit hit, Mathf.Infinity, 1 << 3);

        }

Slight modification so it blocks clicks from other gameobjects as well, not just UI. Need to set layer 3.