Search Unity

Passing an event through to the next object in the raycast

Discussion in 'UGUI & TextMesh Pro' started by mweldon, Sep 4, 2014.

  1. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
    I have a UI where there is a ScrollRect. Inside the ScrollRect are individual items with their own handler for drag events. The problem is that only one drag handler gets called, the first one that the raycast hits. If I click on an item, its handler is called but the ScrollRect's handler does not. If I click between the items, then the ScrollRect's handler gets called. Is there a convenient way allow the event to pass through to the next object in the raycast?
     
    Naught_G likes this.
  2. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
  3. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
    So here's how I got it to work, though it is extremely hacky and it makes our drag component a less reusable than it was before:

    Code (CSharp):
    1.        
    2.         public void OnDrag( PointerEventData eventData )
    3.         {
    4.             // My drag handler for the individual object
    5.  
    6.             // Call the handler for the containing ScrollRect
    7.             if( scrollViewContainer )
    8.             {
    9.                 scrollViewContainer.SendMessage( "OnDrag", eventData );
    10.             }
    11.         }
    12.  
    This appears to work, but it seems like a ham-fisted way to pass events through to a parent object.

    Also as a side note, I tried SendMessageUpwards and it crashed Unity. I'm guessing infinite loop.
     
  4. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,225
    So the way out InputModules are designed is to NOT send messages up like you want to do in this case. This was a design decision on our behalf, but there are use cases where something else is desired. The InputModule is responsible for what events are sent, and how they are sent. You can write your own that has this behaviour if you want.
     
    Fattie likes this.
  5. vrm2p

    vrm2p

    Joined:
    Jul 23, 2014
    Posts:
    17
    Sorry to push this thread up again. There's another circumstance where passing the event to the object "behind" the current object is extremely useful: Drag and Drop to GameObjects with Mouseover

    When trying to drag an object (in my case a die)

    example script
    Code (CSharp):
    1. public void OnDrag (PointerEventData data)
    2.         {
    3.                 Vector3 pos = transform.position;
    4.                 pos.x += data.delta.x;
    5.                 pos.y += data.delta.y;
    6.                 transform.position = pos;
    7.         }
    It sticks to the mousepointer regardless the mousedown position: It's glued to the pointer.

    Moving this pointer to the droptarget (in my case an image) - the pointerenter/pointerexit events arent fired, because the pointer never enters this image. (It does enter, but the object in front of it is blocking)

    The solution here is adding an offset, so that the pointer is never over the draggable target, which is really ugly. Any Ideas on this?

    Edit: This affects OnDrop as well
     
    Last edited: Sep 11, 2014
  6. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,225
    If you look at the included drag and drop example we have you will see that the way we recommend doing this is that when you start a drag you mark the object to ignore raycasting so that it will not block what's behind it :)
     
    mpeddicord likes this.
  7. vrm2p

    vrm2p

    Joined:
    Jul 23, 2014
    Posts:
    17
    Many thanks :)
     
  8. Free286

    Free286

    Joined:
    Oct 29, 2013
    Posts:
    23
    You could try something like this, the implementation is quite amazing I think, as you can pass PointerEventData from OnDrag to OnEndDrag ect.. kudos to the unity team for a good implementation to a hard problem.
    Code (CSharp):
    1. GetComponentInParent<ScrollRect>().SendMessage("OnDrag", eventData);
     
  9. plailabs-dev

    plailabs-dev

    Joined:
    Dec 10, 2012
    Posts:
    12
    A use case may be a draggable object within a vertical scroll list. In this case I should evaluar if te eventDelta has no horizontal component, and if so, the recast should be ignored and passed up not the scroll rect (i.e., the user is not dragging an object but rather scrolling). Is there a way to get through this rather than by the sendMessage approach?
     
  10. plailabs-dev

    plailabs-dev

    Joined:
    Dec 10, 2012
    Posts:
    12
    Here is my working code:
    Code (csharp):
    1.  
    2.  
    3. public static GameObject itemDragged;
    4.  
    5. public void OnBeginDrag (PointerEventDataeventData)
    6. {
    7. if (eventData.delta.x != 0) {
    8.     //My Start Drag
    9.     itemDragged = gameObject;
    10.  } else {
    11.     itemDragged = null;
    12.     GetComponentInParent<ScrollRect>().SendMessage("OnBeginDrag", eventData);
    13.     return;
    14.  }
    15.  }
    16.  
    17. public void OnDrag (PointerEventDataeventData)
    18.  {
    19. if (itemDragged) {
    20.     //My Drag Code
    21.  } else {
    22.     GetComponentInParent<ScrollRect>().SendMessage("OnDrag", eventData);
    23.     return;
    24.  }
    25.  }
    26.  
    27. publicvoidOnEndDrag (PointerEventDataeventData)
    28.  {
    29. if (itemDragged) {
    30.     //My Drop Code
    31.  } else {
    32.     GetComponentInParent<ScrollRect>().SendMessage("OnEndDrag", eventData);
    33.     return;
    34.  }
    35.  }
    36.  
     
    naviln likes this.
  11. Free286

    Free286

    Joined:
    Oct 29, 2013
    Posts:
    23
    After using canvas more there's been a lot of issues, including behavior that is inconsistent on device and pixel perfect not working, as well as text not working with localization. We just use sprite renderers that are sorted based on hierarchy order now, with physics2D raycasts, I recommend this approach until canvas is mature enough to be used in a shippable form.
     
  12. svitlana

    svitlana

    Joined:
    Nov 19, 2013
    Posts:
    1
    Thank you it works!

    You also can call whatever method you need
    gameObject.SendMessage("PointerEnter");
     
  13. DoomGoober

    DoomGoober

    Joined:
    Dec 12, 2013
    Posts:
    7
    brainz12345, good solution. You can don't need SendMessage though, since it's expensive. Just get the parent ScrollRect, cast it to the correct interface, and call it directly. Here's my class that allows BOTH scrolling and dragging another object (my case is different: a scroll view's vertical scroll was keeping the user from dragging its parent horizontally.) Put this MonoBehaviour on the child GameObject of the ScrollRect, called "ViewPort" by default.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using UnityEngine.EventSystems;
    5. using UnityEngine.UI;
    6.  
    7. //Passesdragmessagestothe parent
    8. public class PassDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
    9. {
    10.  private ScrollRect scrollRect;
    11.  public MonoBehaviour passToMonoBehaviour;
    12.  
    13. void Awake()
    14. {
    15. scrollRect = GetComponentInParent<scrollRect>();
    16. }
    17.  
    18.  public void OnBeginDrag(PointerEventData eventData)
    19.  {
    20.  scrollRect.OnBeginDrag(eventData);
    21.  ((IBeginDragHandler)passToMonoBehaviour).OnBeginDrag(eventData);
    22.  }
    23.  
    24.  public void OnDrag(PointerEventData eventData)
    25.  {
    26.  scrollRect.OnDrag(eventData);
    27.  ((IDragHandler)passToMonoBehaviour).OnDrag(eventData);
    28.  }
    29.  
    30.  public void OnEndDrag(PointerEventData eventData)
    31.  {
    32.  scrollRect.OnEndDrag(eventData);
    33.  ((IEndDragHandler)passToMonoBehaviour).OnEndDrag(eventData);
    34.  }
    35. }
    36.  
     
    Tortuap, naviln, vgf555 and 4 others like this.
  14. Jason-Loomis

    Jason-Loomis

    Joined:
    Nov 5, 2014
    Posts:
    1
    For my scripts that implement the IDragHandler interface (and others, like the IPointer... interfaces) where I would like the OnDrag event NOT to be consumed by the first handler that encounters the event, I use SendMessageUpwards to pass the OnDrag event to the ancestors of the object.

    There is an important gotcha though--SendMessageUpwards also sends the message to the script that is sending the message, so sending "OnDrag" from within the OnDrag() event handler will result in an infinite loop. The workaround to make this work in this situation is to call SendMessageUpwards from the object that is the parent of the object to which your script is attached.

    Something like this for OnDrag (and similar for others, e.g. the OnPointer... events):

    Code (CSharp):
    1. void OnDrag(PointerEventData eventData)
    2. {
    3.     if(this.transform.parent != null)
    4.     {
    5.         this.transform.parent.gameObject.SendMessageUpwards("OnDrag", eventData, SendMessageOptions.DontRequireReceiver);
    6.     }
    7. }
     
    IgorAherne likes this.
  15. Brogan89

    Brogan89

    Joined:
    Jul 10, 2014
    Posts:
    244

    This is by far the best solution. Thanks for you're help.
     
  16. PhilippG

    PhilippG

    Joined:
    Jan 7, 2014
    Posts:
    257
    Ha, really elegant, thanks!
     
  17. Ainulindale

    Ainulindale

    Joined:
    Aug 7, 2017
    Posts:
    3
    I don't understand this decision.

    Right now I'm trying to create a feature where you can use middle mouse button to scroll around inside a RectTransform (like you can scroll around with the middle mouse button in a browser) but IPointerDownHandler will not only catch middle mouse button down but left and right mouse buttons as well.

    I don't want to consume left/right mouse events just because I am interested in the middle mouse button. Now I'm at a point where I can't use the built-in event system just because buttons etc behind the scroll RectTransform won't work. And I can't move stuff around so that for example buttons are on top because then they will block the middle mouse button.

    All these send message or GetComponentInParent workarounds are not good solutions because the thing behind the GameObject with a IPointerDownHandler is not guaranteed to be an ancestor of it. RectTransforms can overlap yet be in different hierarchies. You can even have a IPointerDownHandler on a non-canvas GameObject.

    What you could have done, Unity, is require a return on the method catching the event:
    public bool OnPointerDown (PointerEventData eventData);

    That way we could have returned true if we want the event to be stopped. In my case I would return false if I detected a middle mouse button press, and true if left/right was pressed so that the InputModule can continue to send the same event to the next target in its raycast.

    Even ActionScript 3 (flash) have this functionality, to "bubble events" to underlaying objects. They use a different approach with stopPropagation() methods.

    Another thing you could have done if you wanted to still have void as a return for OnPointerDown is to simply add a method to the PointerEventData object named continuePropagation() or similar.

    Nothing would break in old code if you add that method to the eventData object so it's not too late to improve your system without breaking compatibility. Going for this approach would probably be even better than a return value now that I think about it. In my case I would just have to check if middle mouse button was not pressed and then call eventData.continuePropagation() to pass on the event to the next raycast hit (which may have use for it).

    Tim-C: You say that it was a design decision to always consume events once received. But why exactly? I'm always open to improve as a programmer and maybe there is something I've overlooked. From where I'm standing it's strange to require all these people to use flawed workarounds instead of adding the desired functionality to Unity.

    Edit: In PointerEventData there is "used" and "Use()" but they seem to be completely unused by Unity's system. Even if I write my own InputModule, inherit from StandaloneInputModule and override Process() to make base.Process() get called twice -- the "used" field is still false, even though it gets used (consumed) by the OnPointerDown method. I checked to make sure I get the exact same PointerEventData object on the exact same OnPointerDown method as well. Why is used and Use() there?
     
    Last edited: Aug 7, 2017
  18. Tortuap

    Tortuap

    Joined:
    Dec 4, 2013
    Posts:
    137
    I do totally agree.
    Using SendMessage or calling method on interface found on ancestors are crappy workarounds that work for specific very limited context, as you explained.

    Having a method continuePropagation would be a good design solution. Unity guys, are you still there to think about it please ?
     
  19. JesOb

    JesOb

    Joined:
    Sep 3, 2012
    Posts:
    1,109
    +1 for continue propagation

    And one question.

    What is eventData.used for?
    I thought it is exactly for this but seems it just unusable property
     
  20. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    Cmon Unity-guys!
     
    Gamba04 likes this.
  21. GuitarBro

    GuitarBro

    Joined:
    Oct 9, 2014
    Posts:
    180
    Just adding my 2 cents:

    I also find this very unintuitive. At least in my experience with web development, the common paradigm for handling events is to propagate by default. I was expecting something similar to how jQuery handles events (requiring you to explicitly call event event.stopPropagation() if you don't wish for other deeper elements to receive the event). It also doesn't make sense that there appears to be no easy way to have one object consume, say, clicks, but allow other events such as scroll to propagate. It's all events or nothing (or I'm missing something here).

    In my particular case, I wished to detect clicks/touches on overlapping objects and have them both be able to process the response while still allowing UI such as buttons to block propagation. However, it looks like I will be needing to make my own workaround for now. :(
     
  22. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    To be fair, a non-ancestor object behind your GameObject wouldn't receive the event even if you didn't add a pointer handler. Any raycast target will prevent clicks from passing through to hierarchically-unrelated objects behind it, even if it doesn't respond to those events in any way. The additional limitation of adding a pointer handler is only to prevent ancestors from receiving the event.

    I'm pretty sure that already works. If you put a bunch of Buttons inside of a Scroll Rect, the buttons will eat clicks, but drags will pass through and trigger a scroll. In general, I believe components only consume events of a type they handle, and any other events are propagated to the parent object.

    However, as noted above, events only propagate to parents; a sibling will be blocked by any raycast target in front of it, even if it has no event handlers at all.

    If you want to allow pointer events to "pass through" a Graphic, you can set its "raycast target" field to false, or you can use a Canvas Group to turn off raycast hits on an entire hierarchy of objects at once.
     
  23. GuitarBro

    GuitarBro

    Joined:
    Oct 9, 2014
    Posts:
    180
    That is good to know, I should have been more specific as I was referring to siblings, not parents, as you mentioned.

    However, I do believe there still is an issue here. I'm not sure I understand why the raycast should stop at the first hit, or rather, why there is no option to have it hit multiple objects (similar to Physics.RaycastAll()). If performance is a concern, then by all means, leave the current method as the default. But it seems that at least having that as an option would solve many problems.
     
  24. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I imagine one could probably add an invisible full-screen object in front of the world that intercepts pointer events, calls RaycastAll (or something similar), and then propagates the event to everything the ray hits.

    That should be fairly easy for clicks. For enter/leave events it's probably a bit harder, but still fairly doable. (You'd presumably need to check the mouse position every frame, and maintain a list of objects the ray hit last frame.)

    I have not tried this, however.
     
    Deleted User likes this.
  25. Deleted User

    Deleted User

    Guest

    Hey guys. A little late to the party, but here's what I did:

    Code (CSharp):
    1. public class DragPanel : MonoBehaviour,
    2.     IDragHandler, IPointerClickHandler {
    3.  
    4.  
    5.     public void OnDrag(PointerEventData data) {
    6.         // Some dragging logic
    7.     }
    8.  
    9.     public void OnPointerClick(PointerEventData data) {
    10.         // Pass-through click event to all EventTrigger components
    11.         List<RaycastResult> results = new List<RaycastResult>();
    12.         EventSystem.current.RaycastAll(data, results);
    13.         foreach (RaycastResult res in results) {
    14.             EventTrigger trig = res.gameObject.GetComponent<EventTrigger>();
    15.             if (trig == null) {
    16.                 continue;
    17.             }
    18.             trig.OnPointerClick(data);
    19.         }
    20.     }
    21.  
    22. }
    Scene setup for which I made this tiny component:

    Code (text):
    1.  
    2. - Canvas (GraphicRaycaster)
    3.   - DragPanel (stretched to cover the screen)
    4. - Camera (PhysicsRaycaster)
    5. - Sphere (EventTrigger)
    6.  
    So the problem I was solving is pretty similar to what was discussed here, except I wanted to handle dragging on UI (canvas) whilst being able to click objects in 3d space, leading to exactly the same problem (by default this UI panel intercepts all events and doesn't propagate it to siblings). As someone mentioned above, the events are actually dispatched by Unity's Input Modules, so messing with them isn't that great an idea. Instead, I simply had this panel broadcast the click event with RaycastAll, which worked pretty fine.

    I imagine this can be further generalized into something like EventDispatcherPanel which implements all standard event handler interfaces and uses the above logic to broadcast these events. It is also possible to sniff trig.triggers in case you want to dispatch to the first EventTrigger that handles an event of interest. I don't really need anything like that (I just need a drag screen + clicking objects), but hope the above gives an idea how to work around these Unity limitations.

    ---

    Speaking of design decision, I can understand that (software engineer myself), but the design decisions should also be revised based on typical usage scenarios and user stories. I really think that event propagation is something common when working with input, and I can tell thousands (if not more) users are quite frustrated by having to search for some workarounds on forums and re-implement functionality that simply should be there in a first place. </rant> :D
     
    herk99, SamuelIH, ScottAdams and 3 others like this.
  26. joelstartech

    joelstartech

    Joined:
    Mar 6, 2019
    Posts:
    31
    How would I implement this if I had a scrollrect that's full of vertically-stacked full-width buttons that are prefabs? I just want to person to be able to touch scroll through the list, and 'tap' on an item to select it. How would I do that with these scripts?
     
  27. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    You don't need to do anything; that is Unity's default behavior for a scrollrect full of buttons. Taps will interact with the buttons and drags will interact with the scollrect.

    The buttons listen for clicks but not drags, so drags propagate up to the object hierarchy, and the scrollrect is an ancestor of everything in the scrollable container so it will receive them.

    Just make sure the button prefabs don't include any component that would listen for drags.
     
    apelsinex likes this.
  28. joelstartech

    joelstartech

    Joined:
    Mar 6, 2019
    Posts:
    31
    In my use case it simply isn't true. Anytime you try to scroll (or touch drag) over a button, it takes control over the scrollrect. You can scroll/drag *between* the buttons, but it will not scroll when the initial start of the drag happens over a button.
     
  29. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I've just created a scroll view and a bunch of buttons and have no trouble drag-scrolling on top of the buttons.

    Odds are that you have some other component in your button prefabs (not the actual Button component) that is capturing the drags. (My guess would be Event Trigger, which captures pretty much everything whether you need it or not.)
     
  30. martinasenovdev

    martinasenovdev

    Joined:
    May 7, 2017
    Posts:
    67
    this is why we'll switch to Unreal
     
    Gamba04 and MrLucid72 like this.
  31. Harfatum

    Harfatum

    Joined:
    May 28, 2018
    Posts:
    9
    How exactly do you do this? I tried putting this in my OnBeginDrag method but it doesn't seem to do anything:
    Code (CSharp):
    1.  
    2.         gameObject.layer = LayerMask.NameToLayer("Ignore Raycast");
     
  32. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I believe the typical way to remove a UI object from raycasting would be to attach a "Canvas Group" component and uncheck the "Blocks Raycasts" box.
     
  33. Harfatum

    Harfatum

    Joined:
    May 28, 2018
    Posts:
    9
    It's not a UI object. I suppose I could try making it into one.
     
  34. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Are you aware that you're posting in the UI forum?
     
  35. Harfatum

    Harfatum

    Joined:
    May 28, 2018
    Posts:
    9
    Oh, apologies. I've searched what seemed like the whole internet trying to resolve this issue and this seemed like the closest I found, I'll make a topic elsewhere.
     
  36. jhughes2112

    jhughes2112

    Joined:
    Nov 20, 2014
    Posts:
    178
    Since there wasn't a clean implementation posted, here's a solution you can literally just add to a ScrollRect and allow it to pass drag messages to WHATEVER happens to be behind it. This is nice because you can have N pages of prefabs, each with its own vertical ScrollRect, and have a single full page ScrollRect (or custom swiper) that lives behind it, and handles horizontal movement. In the OnBeginDrag, I just look for the next object in the stack and pass all the drag messages to whatever that is. Meaning, your prefabs don't need to be tied to the rest of the scene.

    Enjoy!

    (It should be mentioned that you must declare your OnDragBegin/OnDrag/OnDragEnd to be public functions, or they won't get the SendMessage call.)

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4. using System.Collections.Generic;
    5.  
    6. public class PassDragEvents : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
    7. {
    8.     // Because UIs tend to move around, we collect the target OnBeginDrag and always send the remaining
    9.     // events to that object.
    10.     private GameObject _proxyTarget = null;
    11.  
    12.     void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
    13.     {
    14.         _proxyTarget = null;
    15.         GraphicRaycaster gr = GetComponentInParent<GraphicRaycaster>();
    16.         if (gr != null)
    17.         {
    18.             List<RaycastResult> hits = new List<RaycastResult>();
    19.             gr.Raycast(eventData, hits);  // this will include our game object and any others behind it
    20.  
    21.             bool foundUs = false;
    22.             foreach (RaycastResult rr in hits)
    23.             {
    24.                 if (foundUs)  // grab the very NEXT gameobject
    25.                 {
    26.                     _proxyTarget = rr.gameObject;
    27.                 }
    28.                 else if (rr.gameObject == gameObject)
    29.                 {
    30.                     foundUs = true;
    31.                 }
    32.             }
    33.  
    34.             if (_proxyTarget!=null)
    35.                 _proxyTarget.SendMessage("OnBeginDrag", eventData, SendMessageOptions.DontRequireReceiver);
    36.         }
    37.     }
    38.  
    39.     void IDragHandler.OnDrag(PointerEventData eventData)
    40.     {
    41.         if (_proxyTarget!=null)
    42.             _proxyTarget.SendMessage("OnDrag", eventData, SendMessageOptions.DontRequireReceiver);
    43.     }
    44.  
    45.     void IEndDragHandler.OnEndDrag(PointerEventData eventData)
    46.     {
    47.         if (_proxyTarget!=null)
    48.         {
    49.             _proxyTarget.SendMessage("OnEndDrag", eventData, SendMessageOptions.DontRequireReceiver);
    50.             _proxyTarget = null;
    51.         }
    52.     }
    53. }
    54.  
     
    herk99, MrLucid72 and GuitarBro like this.
  37. naviln

    naviln

    Joined:
    Oct 18, 2016
    Posts:
    32
    Thankyou! This simple example was all I needed to get going. I wanted to get the scrollbar to behave when another button was sitting on top of it. With your example I was able to work out how to pass OnDrag to the Scrollbar, while implementing some of my own custom behaviour (e.g. color and zooming).

    Code (CSharp):
    1. public void OnBeginDrag(PointerEventData eventData)
    2.         {
    3.             HandleDragged();
    4.             //and get the scrollbar to do what it normally does
    5.             GetComponentInParent<Scrollbar>().SendMessage("OnBeginDrag", eventData);
    6.         }
    7.  
    8.         public void OnEndDrag(PointerEventData eventData)
    9.         {
    10.             HandleReleased();
    11.             //scrollbar has no on end drag receiver. hmm
    12.         }
    13.  
    14.         public void OnDrag(PointerEventData eventData)
    15.         {
    16.             HandleDragged();
    17.             GetComponentInParent<Scrollbar>().SendMessage("OnDrag", eventData);
    18.         }
     
    jhughes2112 likes this.
  38. naviln

    naviln

    Joined:
    Oct 18, 2016
    Posts:
    32

    And thank you, for helping me realize that I can call OnDrag or OnBeginDrag directly from the object. Champion.
     
    jhughes2112 likes this.
  39. MrLucid72

    MrLucid72

    Joined:
    Jan 12, 2016
    Posts:
    985
    We have a list that you can scroll through with a scrollable hotspot. It's ridiculous that I can't just check "Raycasting Pass-Through" or something to allow raycasting events through that particular component, which was what was intuitively expected. It took many hours to find this thread that contains the only solutions (and a bit hacky)~

    @Unity, please consider adding something intuitive. If you want to drag and click, click and "scroll through pages" (think flash cards), hover and click (think tooltips), drag and drop (think dice) ... so many situations. My particular situation looked like this:



    These are sort of like flash cards - cheat sheets. If you scroll in the hotspot, it'll show the "next card". However, with the scroll hotzone (green), suddenly I can't hover over icons for tooltips ... then I can't click the button dropdown at the top ... can't do anything. Or If I put it *behind*, it's a buggy hotzone that's not actually a square.

    Intuitively, adding a "pass-through raycast" option would have been a single checkbox to my green hotzone obj for scroll events and that would've been it. So easy and intuitive.
     
  40. jonaslindberg

    jonaslindberg

    Joined:
    Nov 28, 2017
    Posts:
    5
    This is how I solved passing on any PointerEvent type to the next Raycast-blocking item underneath. It's easy to implement some check for a specific Tag, Component etc. if you have many blocking objects stacked...

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. using UnityEngine.EventSystems;
    6.  
    7. public class PassOnPointerEvent : MonoBehaviour, IPointerDownHandler, IDragHandler, IBeginDragHandler, IPointerUpHandler, IEndDragHandler
    8. {
    9.     GameObject newTarget;
    10.  
    11.     public void OnPointerDown(PointerEventData eventData)
    12.     {
    13.         List<RaycastResult> raycastResults = new List<RaycastResult>();
    14.         EventSystem.current.RaycastAll(eventData, raycastResults);
    15.         newTarget = raycastResults[1].gameObject; //Array item 1 should be the one next underneath, handy to implement for-loop with check here if necessary.
    16.         print($"Passing on click to {newTarget}"); //Just make sure you caught the right object
    17.  
    18.         ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.pointerDownHandler);
    19.     }
    20.  
    21.     public void OnPointerUp(PointerEventData eventData)
    22.     {
    23. List<RaycastResult> raycastResults = new List<RaycastResult>();
    24.         EventSystem.current.RaycastAll(eventData, raycastResults);
    25.         newTarget = raycastResults[1].gameObject; //Array item 1 should be the one next underneath, handy to implement for-loop with check here if necessary.
    26.         ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.pointerUpHandler);
    27.     }
    28.  
    29.     public void OnBeginDrag(PointerEventData eventData)
    30.     {
    31.         ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.beginDragHandler);
    32.     }
    33.  
    34.     public void OnDrag(PointerEventData eventData)
    35.     {
    36.         ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.dragHandler);
    37.     }
    38.  
    39.     public void OnEndDrag(PointerEventData eventData)
    40.     {
    41.         ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.endDragHandler);
    42.     }  
    43. }
    44.  
    Hope it can help someone!
     
    Last edited: May 7, 2020
  41. naviln

    naviln

    Joined:
    Oct 18, 2016
    Posts:
    32

    This is a cool idea! Thanks for sharing. I will try this out
     
  42. xzodia

    xzodia

    Joined:
    Jan 24, 2013
    Posts:
    50
    Heres another variation on the above, where you can set the component to pass the drag to in the inspector:
    Code (CSharp):
    1.  
    2.     public Component PassDragXTo;
    3.     public Component PassDragYTo;
    4.  
    5.     Component dragTarget;
    6.  
    7.     public void OnBeginDrag(PointerEventData eventData)
    8.     {
    9.         if (Mathf.Abs(eventData.delta.x) > Mathf.Abs(eventData.delta.y))
    10.         {
    11.             var handler = PassDragXTo as IBeginDragHandler;
    12.             if (handler != null)
    13.             {
    14.                 dragTarget = PassDragXTo;
    15.                 handler?.OnBeginDrag(eventData);
    16.                 return;
    17.             }
    18.         }
    19.         else
    20.         {
    21.             var handler = PassDragYTo as IBeginDragHandler;
    22.             if (handler != null)
    23.             {
    24.                 dragTarget = PassDragYTo;
    25.                 handler?.OnBeginDrag(eventData);
    26.                 return;
    27.             }
    28.         }
    29.  
    30.         dragTarget = null;
    31.     }
    32.  
    33.     public void OnDrag(PointerEventData eventData)
    34.     {
    35.         if (dragTarget != null)
    36.         {
    37.             var handler = dragTarget as IDragHandler;
    38.             handler?.OnDrag(eventData);
    39.             return;
    40.         }
    41.     }
    42.  
    43.     public void OnEndDrag(PointerEventData eventData)
    44.     {
    45.         if (dragTarget !=null)
    46.         {
    47.             var handler = dragTarget as IEndDragHandler;
    48.             handler?.OnEndDrag(eventData);
    49.             return;
    50.         }
    51.     }
     
    GuitarBro likes this.
  43. AdamBebko

    AdamBebko

    Joined:
    Apr 8, 2016
    Posts:
    168
    Sorry I can't find this in the current documentation. Where is that provided example?
     
  44. SamuelIH

    SamuelIH

    Joined:
    Jan 7, 2021
    Posts:
    5
    This saved my day!

    I had to make a couple of changes to get it working for me. Posting it here for completeness:

    Code (CSharp):
    1. private List<IPointerClickHandler> clickHandlers = new List<IPointerClickHandler>(32);
    2. private MonoBehaviour clickBehaviour;
    3.  
    4. public void OnPointerClick(PointerEventData eventData) {
    5.     if (eventData.dragging) return;
    6.  
    7.     // Pass-through click event to all EventTrigger components
    8.     List<RaycastResult> results = new List<RaycastResult>();
    9.     EventSystem.current.RaycastAll(eventData, results);
    10.     foreach (RaycastResult res in results) {
    11.         if (res.gameObject == gameObject) continue; // don't call ourself, yikes!
    12.         res.gameObject.GetComponents<IPointerClickHandler>(clickHandlers);
    13.         foreach (var handler in clickHandlers) {
    14.             clickBehaviour = handler as MonoBehaviour;
    15.             if (clickBehaviour != null && clickBehaviour.enabled) {
    16.                 handler.OnPointerClick(eventData);
    17.                 break;
    18.             }
    19.         }
    20.     }
    21. }
    Notably:
    1. Check for dragging first. For me Unity's still calling the click handler even if I it ends in a drag (?)
    2. Call
      GetComponent
      directly for the
      IPointerClickHandler
      type, which allows me to pass clicks over to any click handler, even if it isn't an event trigger.
    3. Because of #2, make a check so that we don't send clicks back to ourself (inifi-loop)

    Edit #1: Modified the method so that it ignores disabled click handler behaviors.
     
    Last edited: Jul 7, 2022
    sbATI likes this.
  45. unity_8AF450AD4F84143752AF

    unity_8AF450AD4F84143752AF

    Joined:
    Jun 13, 2021
    Posts:
    1
    Thanks Samuell! This is exactly what I needed :)
     
  46. Gamba04

    Gamba04

    Joined:
    Feb 2, 2018
    Posts:
    4
    I decided to post my own compact version of the solutions suggested above. It worked for my needs and I think it solves the issue for a very general use case although I didn't test it outside of my scope.

    The trick that I did was basically disabling the current behaviour (so that it becomes invisible), finding the next handler with the method used in Unity's InputModules,
    ExecuteEvents.GetEventHandler
    , and enabling it back again. It looks something like this:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3.  
    4. public class SampleEventsHandler : MonoBehaviour, IPointerDownHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
    5. {
    6.     private GameObject eventTarget;
    7.  
    8.     void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
    9.     {
    10.         eventTarget = GetEventTarget(eventData);
    11.     }
    12.  
    13.     // Events that you want to pass through
    14.     void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
    15.     {
    16.         Debug.Log("OnBeginDrag");
    17.  
    18.         ContinuePointerEvent(eventData, ExecuteEvents.beginDragHandler);
    19.     }
    20.  
    21.     void IDragHandler.OnDrag(PointerEventData eventData)
    22.     {
    23.         Debug.Log("OnDrag");
    24.  
    25.         ContinuePointerEvent(eventData, ExecuteEvents.dragHandler);
    26.     }
    27.  
    28.     void IEndDragHandler.OnEndDrag(PointerEventData eventData)
    29.     {
    30.         Debug.Log("OnEndDrag");
    31.  
    32.         ContinuePointerEvent(eventData, ExecuteEvents.endDragHandler);
    33.     }
    34.  
    35.     // Helper methods
    36.     private GameObject GetEventTarget(PointerEventData eventData)
    37.     {
    38.         List<RaycastResult> targets = new List<RaycastResult>();
    39.         EventSystem.current.RaycastAll(eventData, targets);
    40.  
    41.         return targets.Count > 0 ? targets[0].gameObject : null;
    42.     }
    43.  
    44.     private void ContinuePointerEvent<H>(PointerEventData eventData, ExecuteEvents.EventFunction<H> eventFunction)
    45.         where H : IEventSystemHandler
    46.     {
    47.         enabled = false;
    48.  
    49.         GameObject nextHandler = ExecuteEvents.GetEventHandler<H>(eventTarget);
    50.         ExecuteEvents.Execute(nextHandler, eventData, eventFunction);
    51.  
    52.         enabled = true;
    53.     }
    54. }
     
    stefan-velnita likes this.
  47. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Suggestion: Any time you find yourself changing a variable temporarily while you do one thing and then changing it back, the "changing it back" part should be in a finally block to make sure that it actually gets changed back, even if an exception occurs. Otherwise, an error in the event handler of the object behind this one could cause this one to be permanently disabled.

    i.e. Change this:
    Code (CSharp):
    1.     private void ContinuePointerEvent<H>(PointerEventData eventData, ExecuteEvents.EventFunction<H> eventFunction)
    2.         where H : IEventSystemHandler
    3.     {
    4.         enabled = false;
    5.         GameObject nextHandler = ExecuteEvents.GetEventHandler<H>(eventTarget);
    6.         ExecuteEvents.Execute(nextHandler, eventData, eventFunction);
    7.         enabled = true;
    8.     }
    to this:
    Code (CSharp):
    1.     private void ContinuePointerEvent<H>(PointerEventData eventData, ExecuteEvents.EventFunction<H> eventFunction)
    2.         where H : IEventSystemHandler
    3.     {
    4.         try
    5.         {
    6.             enabled = false;
    7.             GameObject nextHandler = ExecuteEvents.GetEventHandler<H>(eventTarget);
    8.             ExecuteEvents.Execute(nextHandler, eventData, eventFunction);
    9.          }
    10.         finally
    11.         {
    12.             enabled = true;
    13.         }
    14.     }
     
    Gamba04 and CodeRonnie like this.
  48. Gamba04

    Gamba04

    Joined:
    Feb 2, 2018
    Posts:
    4
    Interesting, that's a nice safety measure to have in some cases. It would be cool if C# allowed you to just "try" something, without a "finally" or "catch" block, I think it would look cleaner in this case.
     
  49. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    What would it mean to "try" something without either "finally" or "catch"?

    In this case, you need a finally block because you have something that needs to happen whether an exception was thrown or not. That's what "finally" does.
     
    Gamba04 likes this.
  50. Gamba04

    Gamba04

    Joined:
    Feb 2, 2018
    Posts:
    4
    At first I thought "try" blocks always stopped the exception from actually happening, and kept the sequence of instructions going after that. I just realized that if you use a "finally" block instead of "catch", the exception will happen and only the instructions inside of the "finally" block will be executed.

    Having this into consideration it makes much more sense to have a "finally" block, and if you want to do the "just try" how I had it in my mind you can write:
    Code (CSharp):
    1. try
    2. {
    3.     // Potential exception
    4. }
    5. catch { }
    6.  
    7. // Continue normal execution