Unity mouse click and hover detection for a 2D project

After fighting with Unity's mouse detection functions I decided it was time to implement a better mouse management class that works with 2D projects.

Hopefully Sébastien Dubois had already written a nice article about the right click detection problem. In his solution, he uses the ability of raycast to ignore specific layers which is great for our purpose. Imagine you want to click on an object behind a transparent object with a collider, it is impossible to do it with Unity mouse today functions.

So what do we need to improve this class is :

  • add 2D support
  • add OnMouseEnter, OnMouseExit and OnMouseOver functions

Adding 2D support

To add 2D support we have to use Physics2D.Raycast function instead of Physics.Raycast function, and replace RaycastHit by RaycastHit2D.

   
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    RaycastHit2D hit;

    if (Physics2D.Raycast(ray.origin, ray.direction, Mathf.Infinity, layerMask))
    {
        hit = Physics2D.Raycast(ray.origin, ray.direction, Mathf.Infinity, layerMask);
        return hit.collider.gameObject;
    }
    else
    {
        return null;
    }
  

Adding OnMouseOver

We can check what is under the mouse every frame using the same method but it would be better to use a coroutine to do the check only at the frequency we need and thus improve performance.

  
    IEnumerator CheckUnderkMouse()
    {
        checkStarted = true;

        // 2D mouse detection
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit2D hit;

        if (Physics2D.Raycast(ray.origin, ray.direction, Mathf.Infinity, layerMask))
        {
            hit = Physics2D.Raycast(ray.origin, ray.direction, Mathf.Infinity, layerMask);
            underMouse =  hit.collider.gameObject;
        }
        else
        {
            underMouse = null;
        }

        yield return new WaitForSeconds(0.1f);
        checkStarted = false;
    }
  

Then just call the right method on each MonoBehaviour in this gameObject with SendMessage method:

  
  underMouse.SendMessage(OnOverMethodName, null, SendMessageOptions.DontRequireReceiver);
  

Adding OnMouseEnter and OnMouseExit

Once we have what is under the mouse, we can store it (in mouseEnter variabale) and compare what is under the mouse in the next frame with what was in the last frame.

Here is the logic:

  1. If we have something under the mouse and nothing stored then we entered something
  2. If we have something under the mouse and it's different from the one we stored then exit the old one and enter the new one
  3. If we have nothing under the mouse and something is stored then exit the one we stored and reset the stored value to null

Note the mouseExit variable is not needed, it is just here for clarity purpose.

   
    if (HandleMouseOver)
    {
        if (!checkStarted)
            StartCoroutine("CheckUnderkMouse");

        // If we have something under the mouse
        if (underMouse != null)
        {
            // Call mouse hover method
            underMouse.SendMessage(OnOverMethodName, null, SendMessageOptions.DontRequireReceiver);

            // If we have nothing stored
            if (mouseEnter == null)
            {
                // Then we entered something
                mouseEnter = underMouse;
                mouseEnter.SendMessage(OnEnterMethodName, null, SendMessageOptions.DontRequireReceiver);
            }
            // If it is different from the one we stored
            else if (underMouse.GetInstanceID() != mouseEnter.GetInstanceID())
            {
                // Exit the old one
                mouseExit = mouseEnter;
                mouseExit.SendMessage(OnExitMethodName, null, SendMessageOptions.DontRequireReceiver);

                // Enter the new one
                mouseEnter = underMouse;
                mouseEnter.SendMessage(OnEnterMethodName, null, SendMessageOptions.DontRequireReceiver);
            }
        }
        else
        {
            // We have nothing under the mouse and something stored
            // call mouse exit method
            if (mouseEnter)
            {
                mouseExit = mouseEnter;
                mouseExit.SendMessage(OnExitMethodName, null, SendMessageOptions.DontRequireReceiver);

                mouseEnter = null;
            }
        }
    }
  

Download the script here.

Many thanks to Sébastien Dubois.

Menu