Detect MouseOver/Click for UI Canvas Object
In my 2D game, there's an inventory system comprised of 20 inventory slots drawn on an UI canvas (displayed/hidden when the 'i' key is pressed).
I'm trying to add some menu functionality to the inventory, where you'd click an item slot and a small pop-up menu would be displayed with multiple, clickable options (equip, drop, info, etc.).
I tried adding a collider to my inventory slot prefab, so I'd be able to use a Raycast to detect when it's under the cursor:
using UnityEngine;
using System.Collections;
public class MouseEvents : MonoBehaviour {
public Camera mainCamera;
// Update is called once per frame
void Update () {
RaycastHit2D hit;
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if(hit = Physics2D.Raycast(ray.origin, new Vector2(0,0)))
Debug.Log (hit.collider.name);
}
}
This works just fine and fills the log when I mouse over a collider, but the inventory canvas is huge in relation to my scene. Like in the unity editor, this canvas is close to 30x larger than the level itself. The way it's displayed make's it look completely normal during runtime, but in the editor it's huge.
I guess what I need to know is:
How can my Raycast detect when the mouse is over an inventory slot, relative to what's being shown on screen at runtime.
Answer by Statement · Nov 07, 2015 at 10:16 PM
How can my Raycast detect when the mouse is over an inventory slot, relative to what's being shown on screen at runtime.
My first reaction for seeing your script was wondering why you weren't using the EventSystem in Unity. Perhaps you don't know about it since it's kind of new or perhaps you have reasons not to. But I'll iterate what you should do to make your game work seamlessly (ish) with pointer events.
So you have a Canvas. I don't know if you thought about it, but the Canvas has what's called a Graphic Raycaster. This is what enables anything at all in the UI to even get a pointer event (mouse click/finger tap etc). The cool thing is that there are different kinds of Raycasters.
Graphic Raycaster is for UI inside a Canvas with a graphic.
Physics Raycaster is for 3D objects with a collider.
Physics 2D Raycaster is for 2D objects with a collider.
You could get rid of your own raycasts and use the Physics 2D Raycaster. To use it, add a Physics 2D Raycaster to your game camera. Unitys Event System (I don't know if you thought about it either, but when you create a canvas, it also creates an event system in the scene) works as such it lets input modules use raycasters to figure out what has been hit on the screen, then the "nearest" object is selected, and given events.
What you need to do on your 2D object is to have a script that handle input. You likely already have a script you can modify. To use these events is easy and there are three steps to it.
using UnityEngine.EventSystems;
Select the interface for the events you want to receive.
Implement the interface methods with the code you want to run when your stuff is clicked.
An example:
using UnityEngine;
using UnityEngine.EventSystems; // 1
public class Example : MonoBehaviour
, IPointerClickHandler // 2
, IDragHandler
, IPointerEnterHandler
, IPointerExitHandler
// ... And many more available!
{
SpriteRenderer sprite;
Color target = Color.red;
void Awake()
{
sprite = GetComponent<SpriteRenderer>();
}
void Update()
{
if (sprite)
sprite.color = Vector4.MoveTowards(sprite.color, target, Time.deltaTime * 10);
}
public void OnPointerClick(PointerEventData eventData) // 3
{
print("I was clicked");
target = Color.blue;
}
public void OnDrag(PointerEventData eventData)
{
print("I'm being dragged!");
target = Color.magenta;
}
public void OnPointerEnter(PointerEventData eventData)
{
target = Color.green;
}
public void OnPointerExit(PointerEventData eventData)
{
target = Color.red;
}
}
Example doesn't work? A list of things to remember to have in case anything was unclear:
EventSystem in the scene to drive input events
Physics 2D Raycaster on the camera to drive hit detection
2D Collider on the Object to enable hit detection
Script on the Object to handle input response
SpriteRenderer with a valid Sprite on the Object so we can see something happening
Answer by Krishx007 · Jul 31, 2019 at 01:37 PM
///Returns 'true' if we touched or hovering on Unity UI element.
public static bool IsPointerOverUIElement()
{
return IsPointerOverUIElement(GetEventSystemRaycastResults());
}
///Returns 'true' if we touched or hovering on Unity UI element.
public static bool IsPointerOverUIElement(List<RaycastResult> eventSystemRaysastResults )
{
for(int index = 0; index < eventSystemRaysastResults.Count; index ++)
{
RaycastResult curRaysastResult = eventSystemRaysastResults [index];
if (curRaysastResult.gameObject.layer == LayerMask.NameToLayer("UI"))
return true;
}
return false;
}
///Gets all event systen raycast results of current mouse or touch position.
static List<RaycastResult> GetEventSystemRaycastResults()
{
PointerEventData eventData = new PointerEventData(EventSystem.current);
eventData.position = Input.mousePosition;
List<RaycastResult> raysastResults = new List<RaycastResult>();
EventSystem.current.RaycastAll( eventData, raysastResults );
return raysastResults;
}
Thank you very much, I think this is the best solution for a canvas.
Thanks man this worked great for me. To get the elements in your update function just type: Debug.Log(IsPointerOverUIElement())
;
You can also add this for loop in GetEventSystemRaycastResults() function to output what UI element you are hovering on.
foreach (var eventData2 in raysastResults)
{
Debug.Log(eventData2.ToString());
}
Answer by Nishat11 · Jan 05, 2018 at 06:31 AM
using UnityEngine.EventSystems;
check if(!EventSystem.current.IsPointerOverGameObject ()) than it will only detect mouse click on blank space not on canvas. you can also do the opposite with same.
Answer by Noctys · Sep 11, 2020 at 05:32 PM
I really like the answer from @Krishx007 -- I simplified it for my needs, figured I would share:
public static bool IsPointerOverGameObject(GameObject gameObject)
{
PointerEventData eventData = new PointerEventData(EventSystem.current);
eventData.position = Input.mousePosition;
List<RaycastResult> raysastResults = new List<RaycastResult>();
EventSystem.current.RaycastAll(eventData, raysastResults);
return raysastResults.Any(x => x.gameObject == gameObject);
}
Oh yeah, and this is my needs:
public static bool IsPointerOverUI(string tag)
{
PointerEventData eventData = new PointerEventData(EventSystem.current);
eventData.position = Input.mousePosition;
List raysastResults = new List();
EventSystem.current.RaycastAll(eventData, raysastResults);
foreach (RaycastResult raysastResult in raysastResults)
{
if (raysastResult.gameObject.CompareTag(tag))
{
return true;
}
}
return false;
}
void Update()
{
if (Utils.IsPointerOverUI("UI")) {
return;
} //-- continue stuff
}
Answer by Cepheid · Nov 07, 2015 at 10:09 PM
Could you not just use Unity's built in UI Events within the UI system? With that you could simply create a public method and assign it to a mouseclick event component on a UI object which would then execute it when clicked.