Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

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

Discussion in 'Input System' started by Michal_Stangel, Jul 19, 2020.

  1. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    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:

    Code (CSharp):
    1.         /// <summary>
    2.         /// CHECK IF MOUSE IS HOVERING OVER UI ELEMENT
    3.         /// </summary>
    4.         public static bool isHoveringUIElement
    5.         {
    6.             get
    7.             {
    8.                 mouseOveredObjectName = "";
    9.                 mouseOveredObjectTag = "";
    10.  
    11.                 if (EventSystem.current == null || EventSystem.current.currentInputModule == null)
    12.                 {
    13.                     return false;
    14.                 }
    15.  
    16.                 var inputModuleType = EventSystem.current.currentInputModule.GetType();
    17.  
    18.  
    19.                 MethodInfo methodInfo;
    20.                 _reflectionCache.TryGetValue(inputModuleType, out methodInfo);
    21.                 if (methodInfo == null)
    22.                 {
    23.                     methodInfo = inputModuleType.GetMethod("GetLastPointerEventData", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
    24.                     _reflectionCache[inputModuleType] = methodInfo;
    25.                 }
    26.  
    27.                 if (methodInfo == null)
    28.                 {
    29.                     return false;
    30.                 }
    31.  
    32.                 var eventData = (PointerEventData)methodInfo.Invoke(EventSystem.current.currentInputModule, new object[] { PointerInputModule.kMouseLeftId });              
    33.  
    34.                 if (eventData != null && eventData.pointerEnter)
    35.                 {
    36.                     mouseOveredObjectName = eventData.pointerEnter.name;
    37.                     mouseOveredObjectTag = eventData.pointerEnter.tag;
    38.                     return eventData.pointerEnter.layer == 5; // 5 is Unity's UI layer
    39.                 }
    40.  
    41.                 return false;
    42.             }
    43.         }
     
  2. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    Well, I did it like this. Hope it's not unnecessarily performance-heavy.


    Code (CSharp):
    1.         public static bool isHoveringUIElement
    2.         {
    3.             get
    4.             {
    5.                 mouseOveredObjectName = "";
    6.                 mouseOveredObjectTag = "";
    7.  
    8.                 PointerEventData pointerData = new PointerEventData(EventSystem.current)
    9.                 {
    10.                     pointerId = -1,
    11.                 };
    12.  
    13.                 pointerData.position = ACore_InputBrain.GetMousePosition_Screen();
    14.  
    15.                 List<RaycastResult> results = new List<RaycastResult>();
    16.                 EventSystem.current.RaycastAll(pointerData, results);
    17.  
    18.                 if (results.Count > 0)
    19.                 {
    20.                     mouseOveredObjectName = results[0].gameObject.name;
    21.                     mouseOveredObjectTag = results[0].gameObject.tag;
    22.  
    23.                     return results[0].gameObject.layer == 5; // 5 is Unity's UI layer
    24.                 }
    25.  
    26.                 return false;
    27.             }
    28.         }
     
    Neil-Corre and looytroop like this.
  3. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,548
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. public class StandaloneInputModulePlus : StandaloneInputModule
    4. {
    5.   // based on solution provided by Numior: https://answers.unity.com/questions/1234594/how-do-i-find-which-object-is-eventsystemcurrentis.html
    6.   public static StandaloneInputModulePlus instance;
    7.   protected override void Awake()
    8.   {
    9.     instance = this;
    10.     base.Awake();
    11.   }
    12.   protected override void OnDestroy()
    13.   {
    14.     instance = null;
    15.     base.OnDestroy();
    16.   }
    17.   public GameObject GameObjectUnderPointer(int pointerId)
    18.   {
    19.     var lastPointer = GetLastPointerEventData(pointerId);
    20.     if (lastPointer != null) return lastPointer.pointerCurrentRaycast.gameObject;
    21.     return null;
    22.   }
    23.   public GameObject GameObjectUnderMouse()
    24.   {
    25.     return GameObjectUnderPointer(PointerInputModule.kMouseLeftId);
    26.   }
    27.   public GameObject GameObjectUnderTouch(Touch touch)
    28.   {
    29.     return GameObjectUnderPointer(touch.fingerId);
    30.   }
    31.   ////////////////////////////////////////////////////////////////////////////
    32.   // ..and this is for testing that it works - delete once you see it's working:
    33.   ////////////////////////////////////////////////////////////////////////////
    34.   private GameObject prevGO = null;
    35.   public override void UpdateModule()
    36.   {
    37.     base.UpdateModule();
    38.     GameObject go = GameObjectUnderMouse();
    39.     if (go != prevGO)
    40.     {
    41.       Debug.Log("mouse is now over " + ((go == null) ? "nothing" : go.name));
    42.       prevGO = go;
    43.     }
    44.   }
    45.   ////////////////////////////////////////////////////////////////////////////
    46. }
    47.  
    Then select the EventSystem object in the scene Hierarchy, go to Inspector, switch to Debug:
    debug_view.jpg

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

    You can access the functions through a reference, or like this:
    Code (CSharp):
    1. GameObject hoveredGameObject = StandaloneInputModulePlus.instance.GameObjectUnderMouse();
    2. if (hoveredGameObject == null)
    3. {
    4.   // no object currently under the pointer
    5. } else ..
     
    Last edited: Jul 20, 2020
  4. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    Thanks. But this solution seems to be based on old input system.
     
  5. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,548
    Ah - indeed, that was lost on me when I read the problem. There's got to be a way without the RaycastAll :/ ..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
     
  6. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    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.

    Code (CSharp):
    1. public class StandaloneInputModuleV2 : StandaloneInputModule
    2. {
    3.     public GameObject GameObjectUnderPointer(int pointerId)
    4.     {
    5.         var lastPointer = GetLastPointerEventData(pointerId);
    6.         if (lastPointer != null)
    7.             return lastPointer.pointerCurrentRaycast.gameObject;
    8.         return null;
    9.     }
    10.  
    11.     public GameObject GameObjectUnderPointer()
    12.     {
    13.         return GameObjectUnderPointer(PointerInputModule.kMouseLeftId);
    14.     }
    15. }
     
  7. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,548
    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.
     
  8. Michal_Stangel

    Michal_Stangel

    Joined:
    Apr 17, 2017
    Posts:
    151
    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.
     
  9. looytroop

    looytroop

    Joined:
    Jun 22, 2017
    Posts:
    69
    This saved me! Thank you!
     
    Neil-Corre and Michal_Stangel like this.