Method to check if mouse is hovering over some UI object.

I have several Canvases with many UI elements and need some method which will return true if mouse cursor currently sits on top of some UI element. I have method for this working in old Input system, however not sure how to approach this in the new Input system.

It reads layer from pointerEnter and returns true if it’s UI layer:

        /// <summary>
        /// CHECK IF MOUSE IS HOVERING OVER UI ELEMENT
        /// </summary>
        public static bool isHoveringUIElement
        {
            get
            {
                mouseOveredObjectName = "";
                mouseOveredObjectTag = "";

                if (EventSystem.current == null || EventSystem.current.currentInputModule == null)
                {
                    return false;
                }

                var inputModuleType = EventSystem.current.currentInputModule.GetType();


                MethodInfo methodInfo;
                _reflectionCache.TryGetValue(inputModuleType, out methodInfo);
                if (methodInfo == null)
                {
                    methodInfo = inputModuleType.GetMethod("GetLastPointerEventData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                    _reflectionCache[inputModuleType] = methodInfo;
                }

                if (methodInfo == null)
                {
                    return false;
                }

                var eventData = (PointerEventData)methodInfo.Invoke(EventSystem.current.currentInputModule, new object[] { PointerInputModule.kMouseLeftId });              

                if (eventData != null && eventData.pointerEnter)
                {
                    mouseOveredObjectName = eventData.pointerEnter.name;
                    mouseOveredObjectTag = eventData.pointerEnter.tag;
                    return eventData.pointerEnter.layer == 5; // 5 is Unity's UI layer
                }

                return false;
            }
        }

Well, I did it like this. Hope it’s not unnecessarily performance-heavy.

        public static bool isHoveringUIElement
        {
            get
            {
                mouseOveredObjectName = "";
                mouseOveredObjectTag = "";

                PointerEventData pointerData = new PointerEventData(EventSystem.current)
                {
                    pointerId = -1,
                };

                pointerData.position = ACore_InputBrain.GetMousePosition_Screen();

                List<RaycastResult> results = new List<RaycastResult>();
                EventSystem.current.RaycastAll(pointerData, results);

                if (results.Count > 0)
                {
                    mouseOveredObjectName = results[0].gameObject.name;
                    mouseOveredObjectTag = results[0].gameObject.tag;

                    return results[0].gameObject.layer == 5; // 5 is Unity's UI layer
                }

                return false;
            }
        }
2 Likes
using UnityEngine;
using UnityEngine.EventSystems;
public class StandaloneInputModulePlus : StandaloneInputModule
{
  // based on solution provided by Numior: https://answers.unity.com/questions/1234594/how-do-i-find-which-object-is-eventsystemcurrentis.html
  public static StandaloneInputModulePlus instance;
  protected override void Awake()
  {
    instance = this;
    base.Awake();
  }
  protected override void OnDestroy()
  {
    instance = null;
    base.OnDestroy();
  }
  public GameObject GameObjectUnderPointer(int pointerId)
  {
    var lastPointer = GetLastPointerEventData(pointerId);
    if (lastPointer != null) return lastPointer.pointerCurrentRaycast.gameObject;
    return null;
  }
  public GameObject GameObjectUnderMouse()
  {
    return GameObjectUnderPointer(PointerInputModule.kMouseLeftId);
  }
  public GameObject GameObjectUnderTouch(Touch touch)
  {
    return GameObjectUnderPointer(touch.fingerId);
  }
  ////////////////////////////////////////////////////////////////////////////
  // ..and this is for testing that it works - delete once you see it's working:
  ////////////////////////////////////////////////////////////////////////////
  private GameObject prevGO = null;
  public override void UpdateModule()
  {
    base.UpdateModule();
    GameObject go = GameObjectUnderMouse();
    if (go != prevGO)
    {
      Debug.Log("mouse is now over " + ((go == null) ? "nothing" : go.name));
      prevGO = go;
    }
  }
  ////////////////////////////////////////////////////////////////////////////
}

Then select the EventSystem object in the scene Hierarchy, go to Inspector, switch to Debug:
6109331--665138--debug_view.jpg

Then scroll down to StandaloneInputModule, click Script and change it to the replacement:
6109331--665141--change_script.jpg

You can access the functions through a reference, or like this:

GameObject hoveredGameObject = StandaloneInputModulePlus.instance.GameObjectUnderMouse();
if (hoveredGameObject == null)
{
  // no object currently under the pointer
} else ..

Thanks. But this solution seems to be based on old input system.

Ah - indeed, that was lost on me when I read the problem. There’s got to be a way without the RaycastAll :confused: …but you can only spend so much time trying to work it out before you’re like why am I still spending time on this lol

I am pretty sure that there is something better than RaycastAll too, but didn’t find anything yet.

Found this code, but failed to make it work with the new input system. Maybe somebody more skillful will know.

public class StandaloneInputModuleV2 : StandaloneInputModule
{
    public GameObject GameObjectUnderPointer(int pointerId)
    {
        var lastPointer = GetLastPointerEventData(pointerId);
        if (lastPointer != null)
            return lastPointer.pointerCurrentRaycast.gameObject;
        return null;
    }

    public GameObject GameObjectUnderPointer()
    {
        return GameObjectUnderPointer(PointerInputModule.kMouseLeftId);
    }
}

Looks like the same thing I learned the approach I’m using from. I actually did try the new input system some time ago but the roadblock turned out to be WebGL on the mobile browser of my phone (Android and Win64 were fine). It was like touch coordinates were inverted in Y (and the “old” input system works fine… so I went with that), dragging up would drag down kind of thing. Maybe that particular issue has been fixed since then… but at this point I’m too far along anyway to change over to the new one anyway without a good reason.

I’ve just concluded ceremony in Project Settings, switched Input Handling from “Both” to “New”.
Ended up with 50 defined Intup Actions in 2 Input Maps. It will still need some tweaking in the future, like this better UI object hovering or that Release interactions fires two times (even if filtered by contex.performed and value) if Input Action has defined more than 1 Binding. But other than that it looks fine.

This saved me! Thank you!

2 Likes