Handling User Input

Most projects you will create in Unity are probably intended to be interactive. They should be able to respond to mouse clicks and drags, keyboard, touch, or other forms of user input. This tutorial will cover a variety of options by which you can manage these types of events.

MonoBehaviour Events

Tip – the methods we will examine here are very quick and easy to use, but I consider them to be “legacy” code. I show it here just in case you encounter it in old projects or code snippets. We will discuss some newer options in later sections.

If you’ve looked through the documentation for MonoBehaviour, you may have noticed that there are a variety of mouse related input events that are readily available. Each of the input methods we will review require that the script is attached to a GameObject which also has a Collider. Let’s do some quick experimenting with these methods:

  1. Open Unity and create a new scene
  2. From the file menu, choose: GameObject -> 3D Object -> Cube
  3. Create a new script called “MonoMouseEvents” and attach it to your cube
  4. Copy the code snippet below into your script
  5. Run your scene and review the messages sent to the Console’s output.
  6. Tip – if needed, you can open the Console from the menu, choose: Window -> General -> Console
  7. Tip – If you feel there are too many messages sent to the console, you can always comment out all of the Debug Logs except for those you want to review.
using UnityEngine;

public class MonoMouseEvents : MonoBehaviour
{
    private void OnMouseDown()
    {
        Debug.Log("OnMouseDown");
    }

    private void OnMouseDrag()
    {
        Debug.Log("OnMouseDrag");
    }

    private void OnMouseEnter()
    {
        Debug.Log("OnMouseEnter");
    }

    private void OnMouseExit()
    {
        Debug.Log("OnMouseExit");
    }

    private void OnMouseOver()
    {
        Debug.Log("OnMouseOver");
    }

    private void OnMouseUp()
    {
        Debug.Log("OnMouseUp");
    }

    private void OnMouseUpAsButton()
    {
        Debug.Log("OnMouseUpAsButton");
    }
}

The code above will post a message to the console every time its relevant method is invoked. The methods are invoked as follows:

  • OnMouseDown – Called when you press down the mouse button while the mouse is over the the GameObject’s collider. It is not called when the mouse is outside the collider’s bounds.
  • OnMouseDrag – Called every frame if the mouse down occurred over the object, and you move the mouse over your object while the mouse is still down. It is not called if the mouse down occurs outside the object, or if you pause too long on mouse down before moving the mouse.
  • OnMouseEnter – Called on any frame where the mouse pointer crosses from having not been over the object, to now being over the object.
  • OnMouseExit – Called on any frame where the mouse pointer crosses from having been over the object, to now not being over the object.
  • OnMouseOver – Called on every frame where the mouse is over the object, whether or not the mouse button is pressed.
  • OnMouseUp – Called on the frame a mouse up occurrs when the mouse is over the object. The mouse down must also have occurred over the object. You can drag around and even off, then back on to the object and still observe a mouse up event.
  • OnMouseUpAsButton – Appears the same to me as OnMouseUp.

Note* According to the documentation, OnMouseUp is supposed to be called even if the mouse down event did not occur on the same collider, but that is not the behaviour I am currently seeing (Unity 2019.3.1f1). If it operated as described in documentation, there would probably be a clearer difference between OnMouseUp and OnMouseUpAsButton.

Input & Input Manager

The Input class is how many developers get other sources of input, such as the axis of a joystick, controller button presses, keyboard input, accelerometer data, and more. It is now implemented within the “InputLegacyModule” indicating that it also is not the newest and best way to obtain user input, but I think it is still the most widely used way of obtaining those inputs.

Ideally when working with this class, you would first configure the Input Manager. You can access this through the menu, choose: Edit -> Project Settings…, and then select “Input Manager” from the column on the left.

The Input Manager lists a bunch of default input settings that are configurable – even by the user at run time. For example, “Fire1” could be invoked by pressing the “left ctrl” keyboard key or the “left mouse” button. You can write your code around observing events for “Fire1” and it will work with either trigger. Later, you or the user may edit your inputs, but since you programmed around an event name, your code will still work.

Open the “MonoMouseEvents” script and append the following method:

private void Update()
{
    if (Input.GetButtonDown("Fire1"))
    {
        Debug.Log(Input.mousePosition);
    }

    var axis = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    Debug.Log(axis);
}

Even though we are talking about input “events”, note that this architecture requires us to “poll” for our Input events every frame. We can use the MonoBehaviour’s “Update” method combined with an “if” condition to handle any polling we need. In this example we handle checking both for joystick and button input, both of which would be expected for even the most basic controller based setup.

At the beginning of our Update method, we are checking for any frame in which the “Fire1” input is first pressed. Remember that by default this could be triggered either by a keyboard key (left ctrl) or by a mouse button (left mouse button). Whenever the “Fire1” event is encountered, the body of our conditional will use another property of the Input class to print the mouse position (in screen coordinates) to our Console’s log.

Then on every frame, we grab the current “axis”, both for the X axis (“Horizontal”) and Y axis (“Vertical”) of our “joystick”. Note that this method has “smoothing” applied. If you trigger the event with keyboard input, you will see that as you hold the button, the printed value will grow from ‘0’ to ‘-1’ or ‘1’ (depending on the button held). If you prefer, you can work without the smoothing by using ‘Input.GetAxisRaw’ instead.

For the sake of clarity, you may wish to comment out one of the Debug Logs, so that you only see the Fire or Axis events one at a time. Go ahead and run the scene and try activating our various input events.

Event Systems

Now let’s start looking at the NEW stuff. In this section we will explore how to observe mouse and touch based input events. There is a bit of setup required to make it function properly:

  • Open Unity and create a new scene
  • Select the “Main Camera” and attach a “Physics Raycaster” component to it
  • From the file menu, choose: GameObject -> UI -> Panel
  • Set the ‘Panel’s’ Anchor presets to use the “middle, center” configuration
  • Make the panel smaller by giving it a width and height of 256 (or another value that looks good to you)
  • From the file menu, choose: GameObject -> 3D Object -> Cube
  • Scale / Position the Cube so that it is partially hidden by the UI Panel and partially exposed. Position (4, 1, 0), Scale (3, 3, 3) – (or other values that looks good to you).
  • From the file menu, choose: GameObject -> Create Empty
  • Parent the ‘Canvas’ and ‘Cube’ GameObjects to the Empty ‘GameObject’ so that the Empty ‘GameObject’ is at the root of the hierarchy and the other objects are its children.
  • Create a new script named “InputHandler” and attach an instance of the script to each of the “GameObject”, “Panel”, and “Cube” GameObjects

When you are done, you should see something that looks like the following:

Some of our setup was necessary to make the Event System functional, and some was just for the sake of our demo. The things which are required are some sort of “Raycaster” as well as an “Event System”. When you work with UI, this is very simple. When we created a Canvas, the canvas already had a “Graphic Raycaster” component, and Unity automatically created an “EventSystem” GameObject for us.

In order to make events also work with 3D objects, we needed to attach a “Physics Raycaster” to our camera. This will let us select the Cube in our scene. Unity also provides a “Physics 2D Raycaster” which we could attach to the camera in order to work with Sprites or other 2D elements that aren’t attached to a Canvas, but we wont be using it in this demo.

Open the InputHandler script and replace its contents with the following code snippet:

using UnityEngine;
using UnityEngine.EventSystems;

public class InputHandler : MonoBehaviour, IPointerClickHandler
{
    private void OnEnable()
    {
        
    }

    void IPointerClickHandler.OnPointerClick(PointerEventData eventData)
    {
        Debug.Log(gameObject.name + " handled click of " + eventData.rawPointerPress.name);
    }
}

The final step to work with the new Event System appears in code. First we needed to add a using statement, “using UnityEngine.EventSystems;” so that we could get access to some new interfaces. Then we made our script conform to the “IPointerClickHandler” interface and implemented its “OnPointerClick” method. Now, whenever the event system detects that something has been clicked, the relevant method will be invoked.

Note that I also added an empty stub method of “OnEnable”. The presence of this method will cause the Unity Inspector pane to display a “checkbox” next to the name of our script so that we can toggle it on and off. We will use this to demonstrate some features of the event system in a moment.

Go ahead and run your scene. Click on the Panel, Cube, and on the overlap of the two, and see what messages appear in the Console window.

You might have expected to see three Console messages for every click of the mouse. What you actually see is that there is only one message for each click. If you click on the Panel, including where it overlaps the Cube, you will see: “Panel handled click of Panel”. If you click on part of the Cube that appears outside the Panel, you will see: “Cube handled click of Cube”. No message ever makes it to our root GameObject instance of the script.

Stop the scene. Now turn off the checkmark next to the Input Handler script name for both the “Cube” and “Panel” GameObjects. Run the scene a second time and try all of the same things. Now a click of the Panel should produce the message: “GameObject handled click of Panel”, and a click of the Cube should produce the message: “GameObject handled click of Cube”.

Stop the scene. Now re-enable the checkmark next to the Input Handler of the Cube. Add a second instance of the Input Handler to the Cube. Run the scene a third time and try clicking the Cube. You should see two messages: “Cube handled click of Cube” appear for every single click.

The important things to take away from these examples are:

  • Only one GameObject will receive the Click Event invocation from the Event System
  • The Event System will favor the first GameObject with a conforming script(s) that it finds, beginning with the GameObject that was interacted with and then proceeding up its hierarchy until the event is actually handled, or there are no handlers.
  • If multiple scripts conform to a Click Event on the Activated GameObject, they will all have an opportunity to respond to the event.

Tip – Not all of the Events in the Event System will be handled the same way as the Click Event. For example, there is an OnPointerEnter event which will be sent to every object up the hierarchy that can handle it. In this case, the GameObject would receive the OnPointerEnter when entering the Cube Or Panel, and the Cube or Panel would only receive the Event when they were the ones triggering the event. You may want to further experiment with each type of event to see how they are handled.

There are several other interfaces that we can conform to, each of which would handle a mouse or touch event similar to the ones we saw earlier on a MonoBehaviour. Here is a complete list of the interfaces and the name of the method to implement:

In case you aren’t familiar with interfaces, you should know that you can make a class conform to as many of them as you wish. This is very unlike the way that you can only inherit from a single other class. Below is an example of a script conforming to three of the above interfaces at the same time:

using UnityEngine;
using UnityEngine.EventSystems;

public class DragHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
    {
        Debug.Log("Begin Drag");
    }

    void IDragHandler.OnDrag(PointerEventData eventData)
    {
        Debug.Log("Dragging");
    }

    void IEndDragHandler.OnEndDrag(PointerEventData eventData)
    {
        Debug.Log("End Drag");
    }
}

Input System

While following along with the section on “Input & Input Manager” above, you might have noticed that the Input class itself is implemented within the “InputLegacyModule” or that the “Input Manager” screen had a big note that mentioned: “Consider using the new Input System Package instead”. Personally I feel that the notice is a bit premature. There is a bit of setup required to swap from the current “Legacy” Input to the new package, and the proposed replacement isn’t even obvious how to obtain. To find it:

  1. From the menu, choose: Window -> Package Manager
  2. In the package manager window, choose Advanced -> Show preview packages
  3. Optionally, filter the list by typing “Input System” into the filter
  4. You may now select and “Install” the package, as well as see links to documentation etc.

Because this is still in preview mode, and therefore may be subject to further changes, I’ll just link to its documentation for now.

Summary

In this lesson, we reviewed various options that Unity has implemented for handling user input. We began with some legacy input options, then looked at both new and upcoming ones. Hopefully we clarified what to use and how to use it, so that you can easily interact with your GameObjects.

If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *