Catch pointer events by multiple gameObjects

I noticed if there is a panel in front of another, mouse events will not reach panels in the back. Is there a way around? I guess it is not working because RayCast is blocked. But can’t RayCasts get multiple hits? Why was it designed like this?

Example Case: The panel on front uses OnPointerClick event and panel on the back uses OnPointerEnter. When you come over panels I want OnPointerEvent on the back panel to be called and when I click I want OnPointerClick to be called.

Use the layers.

You can set the blocking mask directly on your canvas.

UPDATE:

FOUND THE SOLUTION

You need to have your 2 Canvas set to Screen Space Camera and affect the same Camera to the canvas

What you are looking for is [Physics.RaycastAll][1]. This returns all hits along the ray instead of the first one. Of course this means it returns an array of RaycastHits which you will need to loop through.

Here is an example of use. Hopefully this helps in allowing for you to have overlapping objects and multiple events:

void SendEvents()
{
     Ray ray = rayFromMouse; //the ray you are making
     RaycastHit[] hits; //the array of hits. 

     hits = Physics.RaycastAll(rayFromMouse);

     if(hits.Length > 0)
     {
          for(int i = 0; i < hits.Length; i++)
          {
               hits*.transform.GetComponent<MyEventScript>().CallEvent();*

}
}
}
[1]: Unity - Scripting API: Physics.RaycastAll

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…

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

public class PassOnPointerEvent : MonoBehaviour, IPointerDownHandler, IDragHandler, IBeginDragHandler, IPointerUpHandler, IEndDragHandler
{
    GameObject newTarget;

    public void OnPointerDown(PointerEventData eventData)
    {
        List<RaycastResult> raycastResults = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventData, raycastResults);
        newTarget = raycastResults[1].gameObject; //Array item 1 should be the one next underneath, handy to implement for-loop with check here if necessary.
        print($"Passing on click to {newTarget}"); //Just make sure you caught the right object

        ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.pointerDownHandler);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.pointerUpHandler);
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.beginDragHandler);
    }

    public void OnDrag(PointerEventData eventData)
    {
        ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.dragHandler);
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        ExecuteEvents.Execute(newTarget, eventData, ExecuteEvents.endDragHandler);
    }  
}

Hope it can help someone!

It seems like you’re trying to do some sort of event bubbling in a sense.

What you can do, since the front panel is a child of the back panel, is calling the same method in back panel that is called OnPointerEnter by it’s EventTrigger in the front panel’s EventTrigger as well.

In other words, you would make the child intercept the event and send it to it’s parent inside your own callback (whatever public void method you give to the EventTrigger for this).

Hope it works!

If someone will read this…
I found solution for me.

In my case the event is fired for first layer, then to parent layers, but I need only the first event (for top object), so its easy to filter events by frame number:

private int frameNum;
public void OnPointerEnter(PointerEventData eventData)//OnPointerEnter, but you can use any other events
{
      if (frameNum == Time.frameCount) return;
    frameNum = Time.frameCount;

    //Do your stuff here
}

Only the first event will pass (for toppest object).

No one correct solution here, tat’s why I add my one:

public void OnPointerDown( PointerEventData eventData )
{
    
    {
        if( eventData.used ) // Check mark
        {
           
            return;
        }
        eventData.Use(); // Mark object as used

        List<RaycastResult> raycastResults = new List<RaycastResult>();
        EventSystem.current.RaycastAll( eventData, raycastResults );
        foreach( var raycastResult in raycastResults ) // send for all other receivers
        {
            var newTarget = ExecuteEvents.GetEventHandler<IPointerDownHandler>( raycastResult.gameObject );
            ExecuteEvents.Execute( newTarget, eventData, ExecuteEvents.pointerDownHandler );
        }
    }
}