Search Unity

  1. Get all the Unite Berlin 2018 news on the blog.
    Dismiss Notice
  2. Unity 2018.2 has arrived! Read about it here.
    Dismiss Notice
  3. We're looking for your feedback on the platforms you use and how you use them. Let us know!
    Dismiss Notice
  4. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  5. Improve your Unity skills with a certified instructor in a private, interactive classroom. Learn more.
    Dismiss Notice
  6. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  7. Magic Leap’s Lumin SDK Technical Preview for Unity lets you get started creating content for Magic Leap One™. Find more information on our blog!
    Dismiss Notice
  8. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Passing an event through to the next object in the raycast

Discussion in 'Unity UI & 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?
     
  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.
     
    svitlana likes this.
  4. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,044
    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.
     
  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,044
    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. brainz12345

    brainz12345

    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. brainz12345

    brainz12345

    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.  
     
  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:
    1
    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.  
     
  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:
    64

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

    PhilippG

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

    Ainulindale

    Joined:
    Aug 7, 2017
    Posts:
    1
    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
    IgorAherne, Antistone, foobar and 2 others like this.
  18. Tortuap

    Tortuap

    Joined:
    Dec 4, 2013
    Posts:
    29
    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 ?
     
    Jes28 and foobar like this.
  19. Jes28

    Jes28

    Joined:
    Sep 3, 2012
    Posts:
    208
    +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:
    299
    Cmon Unity-guys!
     
  21. GuitarBro

    GuitarBro

    Joined:
    Oct 9, 2014
    Posts:
    17
    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:
    270
    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:
    17
    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:
    270
    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.