Search Unity

  1. Tune in to get all the latest Unity news live from Berlin on June 19, 6pm CEST. Set up a YouTube reminder!
    Dismiss Notice
  2. Unity 2018.1 has arrived! Read about it here
    Dismiss Notice
  3. Scriptable Render Pipeline improvements, Texture Mipmap Streaming, and more! Check out what we have in store for you in the 2018.2 Beta.
    Dismiss Notice
  4. Improve your Unity skills with a certified instructor in a private, interactive classroom. Learn more.
    Dismiss Notice
  5. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  6. 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
  7. 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,010
    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,010
    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:
    22
    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:
    22
    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:
    58

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

    PhilippG

    Joined:
    Jan 7, 2014
    Posts:
    236
    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:
    23
    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:
    198
    +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:
    289
    Cmon Unity-guys!