Kyle Banks

Unity 2D: Detecting GameObject Clicks using Raycasts

Written by @kylewbanks on Dec 17, 2016.

Frequently in Unity you’ll see OnMouseDown used to detect clicks on GameObjects. This works fine but it requires a script on the GameObject itself, and may require synchronization within the game when there are many clickable objects.

Another method, and the one we’ll be going over in this post, is to use a single script to manage all clicks throughout the game. This is useful for a number of reasons, but mainly provides a centralized place to manage all input and to coordinate clicks according to game state. For instance, you may have a box that can only be clicked when the player character is within range, when the menu is closed, and when there is no in-game dialog happening. Placing the click logic within the box’s script in this case would require the box to have knowledge and access to the player, menu, and dialog systems, which can lead to messy and tangled code.

One more issue with handing click events inside your GameObjects is that if you want to support multiple input types, such as clicks and touches, you’ll quickly have bloated GameObject scripts that have to handle too much to do with input events, and not serving their actual purpose.

For these two reasons, I prefer a single input script that handles input events and delegates them to the objects being clicked. This way when a click and/or touch event occurs, you can simply notify the relevant GameObject that it is being interacted with - it doesn’t have to care what means of input it was, just that there was input at all.

In order to set this up simply create an empty GameObject, we’ll call it Click Manager, and attach a new script. Let’s call this script ClickManager.

Click Manager setup

Listening for Clicks

In the ClickManager script, we’ll use Update to check if the mouse has been clicked:

using UnityEngine;
using System.Collections;

public class ClickManager : MonoBehaviour {

	void Update () {
		if (Input.GetMouseButtonDown(0)) {
			Debug.Log("Mouse Clicked");
		}
	}

}

What we’re doing here is using Input.GetMouseButtonDown to detect if the left mouse button, represented by 0, was clicked during the current frame. This means that when you click, even if you hold the button down, this will only return true in one Update loop until the button is released and pressed again. For the right or middle mouse buttons you would use the values 1 or 2 respectively.

What was Clicked?

Now that we know a click occurred, how do we tell what was actually clicked? Since this script doesn’t belong to an actual element of our game (just an empty, non-visual GameObject), we’ll need to use a Raycast to detect what, if anything, was in the spot the user clicked.

A Raycast essentially “draws” a line between two points in the game world, and detects any physics bodies that are hit along the way. You can then use this information to determine what was hit by the Raycast and act accordingly. For another useful example of Raycasts, check out my post on Checking if a Character or Object is on the Ground using Raycasts.

For our purposes, we’ll perform a Raycast from the click location, with zero distance/direction. This means we’ll only get a positive hit from the Raycast if there is an object exactly at the click point - which is precisely what we’re looking for.

One thing to note though: the position of a click is represented by screen space, not world space. Screen space is represented in pixels where 0, 0 is the bottom left of the screen. We’ll need to convert the click position to world space in order to properly compare against the position of our GameObjects. Another minor issue is that the click position contains a z-coordinate, which is irrelevant to us in a 2D game, but will interfere with the Raycast detection since the z-coordinate does still exist in a 2D Unity game, so we’ll need to ignore that coordinate.

First up, inside the if-statement above, we’ll convert the click position to world space:

if (Input.GetMouseButtonDown(0)) {
    Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
}

Using ScreenToWorldPoint we convert Input.mousePosition to world space which can then be used to compare against our GameObjects.

Next we’ll perform the Raycast using Physics2D.Raycast. We’ll provide the mousePos in a Vector2 format to drop the z-coordinate, and use it as our starting point. We’ll also provide Vector2.zero as the direction of the Raycast to ensure only objects located directly at the point of the click are detected:

if (Input.GetMouseButtonDown(0)) {
    Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
    Vector2 mousePos2D = new Vector2(mousePos.x, mousePos.y);

    RaycastHit2D hit = Physics2D.Raycast(mousePos2D, Vector2.zero);
}

Now we can use the RaycastHit2D to determine if anything was hit by the click:

if (hit.collider != null) {
    Debug.Log("Something was clicked!");
}

The hit.collider provides us with a Collider2D which gives us access to the Rigidbody and GameObject that we clicked on. Now we can directly manipulate what was clicked however we see fit! In the following example we simply log the name of the GameObject that was clicked, and add a force to it’s rigidbody:

if (hit.collider != null) {
    Debug.Log(hit.collider.gameObject.name);
    hit.collider.attachedRigidbody.AddForce(Vector2.up);
}

For the purposes of your game, you can do just about anything with the clicked GameObject now that you know what was clicked and how to access it. Happy clicking!

Full Script

using UnityEngine;
using System.Collections;

public class ClickManager : MonoBehaviour {

    void Update () {
        if (Input.GetMouseButtonDown(0)) {
            Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            Vector2 mousePos2D = new Vector2(mousePos.x, mousePos.y);
            
            RaycastHit2D hit = Physics2D.Raycast(mousePos2D, Vector2.zero);
            if (hit.collider != null) {
                Debug.Log(hit.collider.gameObject.name);
                hit.collider.attachedRigidbody.AddForce(Vector2.up);
            }
        }
    }

}
Let me know if this post was helpful on Twitter @kylewbanks or down below!