Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Is there a way to block raycasts?

Discussion in 'UI Toolkit' started by Pikachu_King, Aug 2, 2020.

  1. Pikachu_King

    Pikachu_King

    Joined:
    Oct 10, 2019
    Posts:
    4
    Hello all,

    I am playing around with the UI Toolkit on a game I am making and loving it so far. The issue I am running into is that the UI does not seem to block raycasts, so the scene items behind the UI still get the OnMouseEnter/OnMouseExit events. Is there a way to toggle this so certain items block raycasts in the new UI?

    Thanks!
     
  2. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,203
    We don't have a built-in way to do this. You'll need to implement something on your own using UI Toolkit's Event System to notify your ray casters when to ignore rays.
     
  3. MousePods

    MousePods

    Joined:
    Jul 19, 2012
    Posts:
    753
    Will there be an official release eventually to do this?
     
  4. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,203
    It's something we'll need to address at some point, yes. But it's not on our immediate roadmap for 1.0 so I can't really give any estimates on when it will be implemented.
     
    MousePods likes this.
  5. Pikachu_King

    Pikachu_King

    Joined:
    Oct 10, 2019
    Posts:
    4
    Thank you for the replies, it definitely feels like a basic function of UI to be on even base level parity with old UI system, but for now I will just use a boolean in my scene to determine if the overlay is open and block all mouse enter/exit events.

    Thanks!
     
    tonytopper likes this.
  6. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    I am interested in something like this. Can you suggest or point me in the right direction on how to do this? I have more than just menu's that I need to know if the mouse is over.
     
  7. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,203
    You can have an element that takes up the entire screen (using Absolute position and left/right/top/bottom set to 0), sits behind all of the rest of your UI (by just making it be the first child of your rootVisualElement), and have it capture all Mouse events. If an event made it to this "layer" element, it means no other element has stopped its propagation (used it), so you can than convert the element to a raycast. Otherwise, you just have raycasts disabled.
     
  8. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    What I am asking for is the equivalent of EventSystem.current.IsPointerOverGameObject(). Not sure how the event system will block OnMouseEnter/OnMouseExit, or doing physics queries using the mouse. I would like to block my code from performing those types of operations when the mouse is over the UI.
     
  9. uDamian

    uDamian

    Unity Technologies

    Joined:
    Dec 11, 2017
    Posts:
    1,203
    Right now, you can only know if the mouse is over the UI (and has been used by some UI element) from within the UI Toolkit world - as I roughly described above. This should indeed be equivalent to "EventSystem.current.IsPointerOverGameObject()" if "current" was your previous uGUI element gameobject. It's just a bit more involved right now.
     
    Chris-Trueman likes this.
  10. DeanNorth

    DeanNorth

    Joined:
    May 15, 2015
    Posts:
    4
    I ended up with a solution similar to what uDamian mentioned above and it works well. I'm using the new input system and I was having an issue with the order of events when I had an action bound to left click, it would fire the action before UITK handled the event. The way around this was to bind to virtual mouse left click instead, then capture the event in UITK and when the event needed to propagate, fire off a fake virtual mouse action using the `UnityEngine.InputSystem.OnScreen.OnScreenControl` class. This way I was able to capture UI events correctly.
     
    lang_fox likes this.
  11. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    364
    This works only within one UIDocument it seems.
    When you have multiple UIDocuments layered over each other, uielements of one UIDocument don't block events for the UIDocument below.
     
  12. uBenoitA

    uBenoitA

    Unity Technologies

    Joined:
    Apr 15, 2020
    Posts:
    198
    Starting in Unity 2021.1, we are aiming to make UIToolkit more compatible to use along with partial UI created with the old GameObject-based paradigm (UGUI). As such, UITK runtime panels will be aware of UGUI's EventSystem through a new
    selectableGameObject
    assignable field, which will allow any given runtime panel to be selected by
    EventSystem.current.currentSelectedGameObject
    , and to answer positively to
    EventSystem.current.IsPointerOverGameObject()
    whenever the mouse is over a VisualElement that has
    pickingMode == PickingMode.Position
    .

    When associating a selectableGameObject to a runtime panel, by default UGUI's EventSystem will check for two components:
    PanelRaycaster
    , responsible for returning the panel's information (including sortingOrder) if a ray intersects it, and
    PanelEventHandler
    , implementing all UGUI message handlers and rerouting them to UITK's event loop.

    Those features aren't available in Unity 2021.1's current public alpha, but the alpha that contains them should be coming out shortl. If anyone is interested in playing with it, we will be listening to your feedback of course. Note that none of these features will be forced to be used. If a projects uses UITK and UGUI independently already, we will be providing a method to configure UGUI's EventSystem to behave as though UITK didn't exist, to preserve backwards compatibility.
     
  13. Voronoi

    Voronoi

    Joined:
    Jul 2, 2012
    Posts:
    571
    I get the concept of this, but how exactly do I do this? I am making an AR app, where the user places the object on a plane using ARRaycast. Once they are happy with the placement, they should hit a 'done' button and turn off all the ARPlaneManager stuff. Without blocking, the object jumps to a cast under the button.

    I might have to convert this to a uGUI button, but I really like the UI Tool Kit, and would like to use it for everything if possible.
     
  14. Voronoi

    Voronoi

    Joined:
    Jul 2, 2012
    Posts:
    571
    OK, to answer my own question this is super-ugly but it works. Create a fullscreen Visual Element named 'screen', which contains the button. The button doesn't get the Pointer events.

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.UIElements;
    6. using UnityEngine.XR.ARFoundation;
    7.  
    8. public class PlacementInitializer : MonoBehaviour
    9. {
    10.     public GameObject thingToPlace;
    11.     public UIDocument placementControls;
    12.  
    13.     ARPlaneManager aRPlaneManager;
    14.     PlacementController placement;
    15.  
    16.     void Start()
    17.     {
    18.         aRPlaneManager = FindObjectOfType<ARPlaneManager>();
    19.         placement = FindObjectOfType<PlacementController>();
    20.  
    21.         var root = placementControls.rootVisualElement;
    22.         root?.Q("done")?.RegisterCallback<ClickEvent>(ev => MakeTheThing());
    23.  
    24.         root?.Q("screen")?.RegisterCallback<PointerDownEvent>(ev => DownOverScreen());
    25.  
    26.         root?.Q("screen")?.RegisterCallback<PointerUpEvent>(ev => UpOverScreen());
    27.     }
    28.  
    29.     void DownOverScreen()
    30.     {
    31.         if (placement)
    32.             placement.isOverGameScreen = true;
    33.  
    34.         //Debug.Log("Is Over screen " + Time.time);
    35.     }
    36.  
    37.     void UpOverScreen()
    38.     {
    39.         if (placement)
    40.             placement.isOverGameScreen = false;
    41.  
    42.         //Debug.Log("Is Up Over screen " + Time.time);
    43.     }
    44.  
    45.     void MakeTheThing()
    46.     {
    47.         if (placement)
    48.             placement.isOverGameScreen = false;
    49.  
    50.         //Debug.Log("Is Over Button " + Time.time);
    51.  
    52.         Instantiate(thingToPlace, transform.position, transform.rotation);
    53.  
    54.         DisablePlanesAndManager();
    55.         Destroy(this.gameObject);
    56.     }
    57.  
    58.     void DisablePlanesAndManager()
    59.     {
    60.         if (!aRPlaneManager)
    61.         {
    62.             Debug.LogError("Got no ARPlane Manager!");
    63.             return;
    64.         }
    65.  
    66.         aRPlaneManager.enabled = false;
    67.  
    68.         foreach (ARPlane arp in aRPlaneManager.trackables)
    69.         {
    70.             arp.gameObject.SetActive(false);
    71.         }
    72.  
    73.         ARRaycastManager aRRaycastManager = FindObjectOfType<ARRaycastManager>();
    74.         if (aRRaycastManager)
    75.             aRRaycastManager.enabled = false;
    76.  
    77.  
    78.         if (placement)
    79.             placement.enabled = false;
    80.     }
    81.  
    82. }
    83.  
     
    gareth_untether likes this.
  15. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    364
    I created something like this in our project, only with serveral uidocuments.
    Unforunatelly in 2021.1.0b8 this does not work anymore because you can't klick through the invisible Fullscreen VisualElement anymore (the UI document below does not register any clicks because of that)
     
  16. Gorebunny

    Gorebunny

    Joined:
    Feb 17, 2013
    Posts:
    2
    Running into this same problem. It would be nice for PC games to have a proper integrated solution, but I'm okay with rolling my own. However, I'm stuck with an issue of how to pass on mouse events which are blocked by transparent portions of images.

    I'm using both vector graphics to mask images and images with transparency and in both cases the rectangle of the visual element is capturing all mouse events, but I need to be able to only capture those over non-transparent, non-masked pixels so I can pass on the others.

    I'm not sure what the solution is but without one the UI Toolkit is basically unusable for PC games that use the mouse.
     
  17. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    2022 and frankly, it's pathetic that the new input system doesn't handle this basic case. Almost every game has some UI elements that when interacted with should NOT propagate the events further into the 3D scene behind. The old solution with IsPointerOverGameObject was already clunky, but at least it worked. Offer nothing comparable in the new input system is a joke, sorry for being rude but it is.

    I positively regret moving my project over to the new input system.
     
  18. koirat

    koirat

    Joined:
    Jul 7, 2012
    Posts:
    2,008
    I was planing to jump into UIToolkit.
    Now I'm starting to worry, can you please answer one question.
    Can we check if cursor is over GUI elements ? (flag,event,anything )
     
  19. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    According to https://docs.unity3d.com/Packages/c...l#handling-ambiguities-for-pointer-type-input the recommended ways are essentially "build your game so this problem doesn't appear" or "poll the InputSystem in Update()" (which defeats half of the point of using it, but hey) or "use EventSystem.
    RaycastAll" (because the stupid Input System didn't already make TWO raycasts (graphics and physics), so let's make the exact same ones again, because why not?

    Is it really so frigging difficult to store "cursor is currently over UI element" somewhere when you're doing graphics raycast every frame anyways?
     
  20. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    You can use the EventSystem, they made a version that works with UI elements. So you get the "clunky" IsPointerOverGameObject() with UI Elements. It also makes it work with the new Input System. The link you posted actually states that you can use it. It's a solution that works without any extra ray casts.
     
  21. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    Wrong.

    The link specifically warns against using it, and so does an in-game warning in the console. And if you ignore the warning and do it anyways (I've tried that) you get false results.

    If it is so easy, please fill in this example code:

    Code (CSharp):
    1. using UnityEngine.InputSystem;
    2.  
    3. public class Something : Monobehaviour {
    4.  
    5.         [SerializeField] InputActionReference LeftMouseClick;
    6.  
    7.         void OnEnable() {
    8.             LeftMouseClick.action.performed += (InputAction.CallbackContext ctx) => {
    9. if (SomeWayToDetermineThisClickAlsoHitSomeUIElement() == false) { HandleClick(); }
    10. };
    11.         }
    12.  
     
  22. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    Code (CSharp):
    1. [SerializeField] InputActionReference LeftMouseClick;
    2. private bool pointerOverUI = false;
    3.  
    4. void OnEnable()
    5. {
    6.     LeftMouseClick.action.performed += (InputAction.CallbackContext ctx) =>
    7.     {
    8.         if (pointerOverUI ==  false) { HandleClick(); }
    9.     };
    10. }
    11.  
    12. private void Update()
    13. {
    14.     pointerOverUI = EventSystem.current.IsPointerOverGameObject();
    15. }
    16.  
     
  23. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    That was literally the 2nd thing I tried and it does not work. pointerOverUI is always true as long as the pointer is somewhere in the editor window.

    Sorry if I'm angry, but switching to the new input system completely broke my game, my players are angry, basic things that shouldn't be an issue are broken (my friggin main menu which is just UI buttons stopped working!) and I regret doing it so much I'm considering switching everything to Unreal Engine just to get away from this broken crap with unfinished documentation.
     
  24. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    You might have a VisualElement covering the entire canvas that is making it true, happened to me when I first started using the new Input System with UITK.
     
  25. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    Negative on that. Thanks for trying to help. But I think there are deeper issues at work here and I'm trying to figure it out despite the incomplete documentation and nonsense API. Going to waste another two days on something I should've never started (if only the old input system would support runtime configuration, I surely would've stayed with it, everything worked flawlessly :-( )
     
  26. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    Have you checked the debugger. Select your panel from the drop down and take a look, it might help.
     
  27. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    I'll try. But it definitely isn't my canvas or any of my UI elements - I can go into play mode and simple set the whole thing to inactive and the pointerOverUI is STILL true. Only when I move the mouse out of the editor window does it go false.

    I disabled EVERYTHING and then it goes false. That was the clue - it reacts to ALL gameobjects, not just UI elements.

    Why? Because my main camera has a physics raycaster. If I disable that, I get the intended behaviour.

    Except that I can't disable that because otherwise my 3D scene doesn't get pointer events (which obviously I need, it's an RTS style game). So yeah... on to more wasted time on things that should be a simple bool somewhere in the stupid InputSystem.
     
  28. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    An hour of F***ing around, reading docs, stackoverflow and a lot of trial & error later, here is code that works for anyone else who has this problem:

    This works for me because I put all my UI elements on their own layer for other reasons. I don't see a way of making this work without doing that, but maybe someone smarter than me (and less angry about how messy all this is) can figure that out:


    Code (CSharp):
    1.  
    2.         [SerializeField] LayerMask UI_Layers;
    3.  
    4.         private bool IsPointerOverUI() {
    5.             PointerEventData pointer = new PointerEventData(EventSystem.current);
    6.             pointer.position = Mouse.current.position.ReadValue();
    7.             List<RaycastResult> raycastResults = new List<RaycastResult>();
    8.             EventSystem.current.RaycastAll(pointer, raycastResults);
    9.          
    10.             if (raycastResults.Count > 0) {
    11.                 foreach(RaycastResult result in raycastResults) {
    12.                     if ((UI_Layers & (1 << result.gameObject.layer)) != 0) {
    13.                         return true;
    14.                     }
    15.                 }
    16.             }
    17.             return false;
    18.         }
     
    snw and kirillichz941 like this.
  29. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,256
    Yeah, I see that all your objects are also a part of the EventSystem. You can set the PhysicsRaycaster to only pick certain layers, but not the EventSystem its self, but then again you need the event system to be able to pick the units and such. Yeah that would require a custom solution like you have done, not sure if it is the best myself as IsPointerOverGameObject() does some caching so it knows what its over at the beginning of the frame. I'd stick with what you got or make your RTS work without the event system.
     
  30. qwert024

    qwert024

    Joined:
    Oct 21, 2015
    Posts:
    43
    Hi,
    thanks for posting the code!
    I was wondering how do you assign layers for your UI elements? since UI elements in UI Toolkit are not even in the "classic" scene gameobject hierarchy.
    It would be great if you could share your workflow!
     
  31. Tom163

    Tom163

    Joined:
    Nov 30, 2007
    Posts:
    1,272
    I'm using classic UI objects, so they are part of the scene.

    And after my experience with the Input System, I'm not touching anything from Unity outside the main branch with a 10 foot pole for a long time.
     
  32. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    364
    UI Elements dont have layers.
    But you can use the following trick:
    Since UI elements are rendered not in the 3D world but in screen space, just check if the object the raycast hit isValid and has distance = 0.
     
  33. qwert024

    qwert024

    Joined:
    Oct 21, 2015
    Posts:
    43
    Thank you both for the reply!
    What I want to do is to use raycast to detect which UI element I hit (I want to know if I hit button01, button02, panel01, etc.), without using the classic "button01.clicked += DoSomething()" way
    This is what I have:
    -- screen (VisualElement)
    ---- menu (VisualElement)
    ------- btn01 (Button)
    ------- btn02 (Button)
    upload_2022-1-13_10-53-27.png

    With the following code I can detect if my mouse is hovering on the UI. However result.gameObject.name is always "PanelSettings".
    I was wondering if there is a way to know what specific UI element is being hit?

    Code (CSharp):
    1.  void Update()
    2.     {
    3.      
    4.         PointerEventData pointer = new PointerEventData(EventSystem.current);
    5.         pointer.position = Mouse.current.position.ReadValue();
    6.         List<RaycastResult> raycastResults = new List<RaycastResult>();
    7.  
    8.         // this can hit ui with "picking mode" set to "position"
    9.         EventSystem.current.RaycastAll(pointer, raycastResults);
    10.        
    11.         Debug.Log($"count: {raycastResults.Count}");
    12.         if (raycastResults.Count > 0)
    13.         {
    14.             foreach (RaycastResult result in raycastResults)
    15.             {
    16.                 Debug.Log($"name: {result.gameObject.name}");
    17.             }
    18.         }      
    19.     }
    result of the above code:
    upload_2022-1-13_10-55-49.png
     

    Attached Files:

  34. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    364
    I don't think that will work.

    a) why do you want to do that anyway, since there are the ui events for that?
    b) you could calculate the screenposition of the buttons
    c) you could create world collider of the size of the buttons and on there position for you raycast.
     
  35. qwert024

    qwert024

    Joined:
    Oct 21, 2015
    Posts:
    43
    hmm it's complicated to explain. Due to our design, we prefer not to use the classic ui events which relies on unity.
    Thank you very much for the suggestions! I will try these workaround to see if we still want to avoid the classic way.
     
  36. restush96

    restush96

    Joined:
    May 28, 2019
    Posts:
    120
    "Is there a way to block raycasts?"

    Partially yes, you can add a visual element as blocker ui on the bottom of your ui that want to block. Then you can disable or enable the blocker ui.