Monthly Archives: November 2015

Drag-and-Drop

Drag-and-drop functionality is useful for many types of games – an inventory system in an RPG, dragging letters onto a word game board, etc. With a simple script you can add drag-and-drop functionality that will work with both mouse and touch input. This is also a perfect problem with which to demonstrate my technique for simplifying complex functionality.

drag-and-drop

My Technique

I like to break down a problem into its constituent parts, and tackle each separately.

For drag-and-drop I figured I needed to know the following:

  • Is the player touching the screen? And where?
  • Is the player currently touching/dragging an object?
  • Has the player’s input changed since the last frame (has she started or ended a touch gesture?)
That’s all that is needed for drag-and-drop. Three simple problems that are each easily solved. Each of these steps will roughly correspond to a method in my final script.
 
Sometimes I’ll use drawings or props to help visualise the small steps that make up a larger problem. For example, put a small object on your desk, then mimic dragging it around and take note of what you do, and what information is being generated by the action. Almost anything in a game is a simulation of a real-world action.

Competencies

I will assume you know Unity basics, but if anything here goes over your head, please refer back to some of my earlier posts (such as my Pong clone for beginners) to fill in your gaps. There is nothing particularly advanced in here, but I will by necessity use some Unity terminology along the way.

As usual you can download my sample project or follow along, or some combination of the two.

Create a Unity Project

Create a new 2D Unity project. You can add this script to an existing project, but only do that if you’re comfortable with it. It will be easier to follow if you’re inexperienced if you create a new project.

Create an Object to Drag

Let’s create a simple object with a sprite and a collider. You can use any image you want. Some public domain Kenney images are in the sample project if you want to use those.

Note: I like to use Kenney art. Kenney makes great art and releases it into the public domain so that people making games (or tutorials) can make something that doesn’t look rubbish. Please consider buying a subscription to Kenney’s art (or simply download the tons of free assets) from his website: http://kenney.itch.io/kenney-donation

Create a new GameObject in the scene and add the following components:

  • A SpriteRenderer with an image of your choosing. Make sure it’s big enough to drag around with your mouse or finger.
  • A Collider2D. Use either BoxCollider2D or CircleCollider2D.
Rename your object to match the image you used.
The Collider2D is used to detect when you are touching the object, and of course the SpriteRenderer gives the object its appearance.
Make sure to add the Collider2D after adding the sprite, that way the collider will automatically conform to the sprite’s shape.
You should avoid PolygonCollider2Ds for this kind of use, as the accuracy is not required, and they are less efficient than the simpler circle and rectangle colliders.

The InputManager Script

I’ll go through the script piece-by-piece, and then give you the complete script at the end.

Methods

We only have two methods (plus the Update() MonoBehaviour method):

  • DragOrPickUp() – if an item is being dragged, move it with the input; if an object is not being dragged, pick up an object that’s being touched.
  • DropItem() – releases a picked up item
  • Update() – calls the above methods when required according to input and property values (below).

Properties and Variables

  • CurrentTouchPosition – returns the position of a detected touch/mouse input
  • HasInput – returns true when the player is currently touching the screen/holding the mouse button

We also need a few variables:

  • draggingItem – whether the player is currently dragging an item
  • draggedObject – holds a reference to an object being dragged
  • touchOffset – allows a grabbed object to stick realistically to the player’s touch position (more about this later).

I’ll go through each method separately, and show the full script afterwards, so feel free to jump to the full script or read the following sections to see how each part works.

Update()

The Update() method is quite simple:

void Update ()
{
    if (HasInput)
    {
        DragOrPickUp();
    }
    else
    {
        if (draggingItem) DropItem();
    }
}

I like to keep methods like Update() free of too much logic, so, as in this example, the method becomes almost a plain-English sequence of events. In this case we just check if there is input from the player, and we either pick up or move the selected object (if there is input) or drop any currently picked up object (if there is no input).

You may think that some of the small methods and properties in this script could easily be added to Update(), and you wouldn’t be wrong. However, I like to keep my code modular, which helps readability and debugging. It also allows me to add extra functionality quite easily later.

HasInput

I’ve implemented HasInput as a bool property. It checks if the player is currently interacting via mouse click or touch.

Note: I’ve used Input.GetMouseButton(0). In Unity, touch input also counts as GetMouseButton(0), as Unity abstracts away input to some extent. To detect touch directly you can use:


return Input.touchCount > 0; // returns true if at least one touch is detected on the screen

 

See the Unity documentation for more details about the various input methods.

private bool HasInput
{
    get
    {
        // returns true if either the mouse button is down or at least one touch is felt on the screen
       return Input.GetMouseButton(0);
    }
}

CurrentTouchPosition

This property returns the current position of a touch. We get this position during dragging and picking up objects.

The input position is attained via Camera.main.ScreenToWorldPoint using either the current input position. Basically, Camera.main.ScreenToWorldPoint lets us compare an input position with the position of items within our scene (the input is in screen space, but our scene objects are in world space co-ordinates).

DragOrPickUp()

This method is the most complex of the script. It is called when there is touch input detected, and it does one of the following:

  • If an object is already picked up, that object is moved according to the input’s current position
  • If an object is not already picked up, it checks if the current input it touching an object (using  a raycast), and if so picks up that object by storing it in the draggedObject variable and setting the draggingItem variable to true.
private void DragOrPickUp()
{
    var inputPosition = CurrentTouchPosition;
    if (draggingItem)
    {
        draggedObject.transform.position = inputPosition + touchOffset;
    }
    else
    {
        RaycastHit2D[] touches = Physics2D.RaycastAll(inputPosition, inputPosition, 0.5f);
        if (touches.Length > 0)
        {
            var hit = touches[0];
            if (hit.transform != null)
            {
                draggingItem = true;
                draggedObject = hit.transform.gameObject;
                touchOffset = (Vector2)hit.transform.position - inputPosition;
                draggedObject.transform.localScale = new Vector3(1.2f,1.2f,1.2f);
            }

        }
    } 
}

After caching the current input position, we check if the player is already dragging an item (the draggingItem bool variable), and if so the dragged object’s position is changed to the input’s position (minus the offset – more on that in a moment). This means the dragged object effectively sticks to the player’s finger.

However, if no item is being dragged we need to check if the current touch (this method is only called when we have input from the player) is hitting an object that can be dragged. We use RaycastHit2D to detect all colliders under the player’s finger. Because this is a simplified script we just take the first object that is ‘hit’ by the raycast (if there is one) and that object is picked up by being stored in the draggedObject variable, and we set the draggingItem bool to true to indicate that we are now in dragging mode.

I’ve also added scaling to the dragged object to make objects a little larger when being dragged.

The Offsetdrag_offset_image

When picking up an object we also set an offset value that is used when moving it. This offset makes the object stick to where your finger made initial contact with it.

When you move a transform you are telling Unity where the centre of the object goes (normally). When dragging an object, the item being dragged will snap so that its centre is where your finger is, which will feel jerky and unnatural. The offset makes it so the object moves relative to your finger (it basically remembers where the centre of the object should be relative to your finger based on where you first touched the finger).

If you want to see what happens without the offset, make touchOffset equal Vector2.zero when the object is first picked up, then drag around an object by its edge.

DropItem()

The last method is used to ‘drop’ an object when the touch input stops. It’s pretty self explanatory, and simply sets the draggingItem bool to false, which means the rest of the script knows that an object is not being dragged, then sets the dragged object’s scale back to 1.

The Full Script

Create a new C# script called InputManager, and put the following code in it:

Note: In Unity, a script’s name must match the class name, so make sure you call your script InputManager.cs or adjust the class name to match whatever name you give it.
using UnityEngine;
using System.Collections;
public class InputManager : MonoBehaviour
{
    private bool draggingItem = false;
    private GameObject draggedObject;
    private Vector2 touchOffset;
  
    void Update ()
    {
        if (HasInput)
        {
            DragOrPickUp();
        }
        else
        {
            if (draggingItem)
                DropItem();
        }
    }
    
    Vector2 CurrentTouchPosition
    {
        get
        {
            Vector2 inputPos;
            inputPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
            return inputPos;
        }
    }

    private void DragOrPickUp()
    {
        var inputPosition = CurrentTouchPosition;
    
        if (draggingItem)
        {
            draggedObject.transform.position = inputPosition + touchOffset;
        }
        else
        {
            RaycastHit2D[] touches = Physics2D.RaycastAll(inputPosition, inputPosition, 0.5f);
            if (touches.Length > 0)
            {
                var hit = touches[0];
                if (hit.transform != null)
                {
                    draggingItem = true;
                    draggedObject = hit.transform.gameObject;
                    touchOffset = (Vector2)hit.transform.position - inputPosition;
                    draggedObject.transform.localScale = new Vector3(1.2f,1.2f,1.2f);
                }
            }
        }
    }

    private bool HasInput
    {
        get
        {
            // returns true if either the mouse button is down or at least one touch is felt on the screen
            return Input.GetMouseButton(0);
        }
    }

    void DropItem()
    {
        draggingItem = false;
        draggedObject.transform.localScale = new Vector3(1f,1f,1f);
    }
}

Add the Script to the Scene

Create an empty GameObject in the scene and add the above script to it.

What Next?

There are lots of ways to improve this functionality. You should probably use tags or layers to determine which objects can be picked up. If you’re using physics you may need to disable colliders when an object is being moved. You might need to implement logic for when two items overlap and  you need to determine which one the player is trying to touch.