Passing an event through to the next object in the raycast

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 Likes

Maybe this is a duplicate question with this one: Detect drag but pass through all other events? - Unity Engine - Unity Discussions

1 Like

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:

        public void OnDrag( PointerEventData eventData )
        {
            // My drag handler for the individual object

            // Call the handler for the containing ScrollRect
            if( scrollViewContainer )
            {
                scrollViewContainer.SendMessage( "OnDrag", eventData );
            }
        }

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 Likes

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.

1 Like

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

public void OnDrag (PointerEventData data)
        {
                Vector3 pos = transform.position;
                pos.x += data.delta.x;
                pos.y += data.delta.y;
                transform.position = pos;
        }

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

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 :slight_smile:

1 Like

Many thanks :slight_smile:

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.

GetComponentInParent<ScrollRect>().SendMessage("OnDrag", eventData);

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?

Here is my working code:

public static GameObject itemDragged;

public void OnBeginDrag (PointerEventDataeventData)
{
if (eventData.delta.x != 0) {
    //My Start Drag
    itemDragged = gameObject;
 } else {
    itemDragged = null;
    GetComponentInParent<ScrollRect>().SendMessage("OnBeginDrag", eventData);
    return;
 }
 }

public void OnDrag (PointerEventDataeventData)
 {
if (itemDragged) {
    //My Drag Code
 } else {
    GetComponentInParent<ScrollRect>().SendMessage("OnDrag", eventData);
    return;
 }
 }

publicvoidOnEndDrag (PointerEventDataeventData)
 {
if (itemDragged) {
    //My Drop Code
 } else {
    GetComponentInParent<ScrollRect>().SendMessage("OnEndDrag", eventData);
    return;
 }
 }
1 Like

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.

Thank you it works!

You also can call whatever method you need
gameObject.SendMessage(“PointerEnter”);

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.

using UnityEngine;
using System.Collections;
using UnityEngine.EventSystems;
using UnityEngine.UI;

//Passesdragmessagestothe parent
public class PassDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
 private ScrollRect scrollRect;
 public MonoBehaviour passToMonoBehaviour;

void Awake()
{
scrollRect = GetComponentInParent<scrollRect>();
}

 public void OnBeginDrag(PointerEventData eventData)
 {
 scrollRect.OnBeginDrag(eventData);
 ((IBeginDragHandler)passToMonoBehaviour).OnBeginDrag(eventData);
 }

 public void OnDrag(PointerEventData eventData)
 {
 scrollRect.OnDrag(eventData);
 ((IDragHandler)passToMonoBehaviour).OnDrag(eventData);
 }

 public void OnEndDrag(PointerEventData eventData)
 {
 scrollRect.OnEndDrag(eventData);
 ((IEndDragHandler)passToMonoBehaviour).OnEndDrag(eventData);
 }
}
7 Likes

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):

void OnDrag(PointerEventData eventData)
{
    if(this.transform.parent != null)
    {
        this.transform.parent.gameObject.SendMessageUpwards("OnDrag", eventData, SendMessageOptions.DontRequireReceiver);
    }
}
1 Like

This is by far the best solution. Thanks for you’re help.

Ha, really elegant, thanks!

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?

16 Likes

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 ?

4 Likes

+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

Cmon Unity-guys!

1 Like