Drag and Drop in Unity Part Two

After getting a basic system of moving Items between different Containers, I needed to extend this slightly. First of all, I need some feedback of which container an item is going to, and also of restricting items to only some containers. In most games, only some containers are valid locations to drop an item.

Feedback

What I want is for the containers to highlight when an item is hovering over them and they can be dropped. To detect when an item is hovering, I gave all the Containers a BoxCollider2D component and all of the Items both a RigidBody2D component and a BoxCollider2D. Now apparently, the collider should size its mesh to the shape of the GameObject but since I am only using Panels, not sprites, this doesn’t seem to be the case. It’s not a problem, since the collider mesh can be edited manually.

The RigidBody2D components need to be set to Kinematic for the Body Type, so that the Unity engine doesn’t try to push them with gravity all over the place.

The BoxCollider2D elements on the Containers need to have Is Trigger set to true, so that the trigger methods fire.

Then on the DropHandler script I added the following methods (which are overrides of methods on the MonoBehavior base object so they don’t need any more interfaces):

private Color? _startingColour;
 
private void OnTriggerEnter2D(Collider2D collision)
{     var image = GetComponent<Image>();     _startingColour = _startingColour ?? image.color;     if (collision.transform.parent.gameObject != gameObject)     {         image.color = Color.red;     }
}
 
private void OnTriggerExit2D(Collider2D collision)
{     if (collision.transform.parent.gameObject != gameObject)         GetComponent<Image>().color = _startingColour.Value;
}

This will make the Container red when something is hovering, and white otherwise, but only if the container is not it’s original parent container. The _startingColour field is to be able to return to whatever it was before. The last part of the puzzle is to add the following to the OnDrop method in the DropHandler script:

GetComponent<Image>().color =

_startingColour ?? GetComponent<Image>().color;

So that after an item has been dropped, the container will return to it’s original colour.

Restrictions

Restrictions were surprisingly easy. First of all, everything got renamed either ContainerAA, ContainerBB, ItemAA and ItemBB so I could check conditionals without having to go through added a custom editor pane. Then, I added the CanDrop method to the DropHandler script:

public bool CanDrop()
{     return (DragHandler.ItemBeingDragged.name.Contains("AA") && gameObject.name.Contains("AA"))         || (DragHandler.ItemBeingDragged.name.Contains("BB") && gameObject.name.Contains("BB"));
}

And called it as part of the conditional checks on OnDrop, OnTriggerEnter2D and OnTriggerExit2D.

Et voila! Only some targets highlighted on hover, and those were the only ones that would accept the drop. Different items had different targets.

In further developing this, I can see that CanDrop will really be looking for a specific MonoBehavior type (for example, by calling ItemBeingDragged.GetComponent<Card>()) and then interrogating the properties against specific logic – different for each container. So in Solitaire, the CanDrop method would get the Card component out of the item being dragged and then check it’s suit and value against the current suit and value on it’s own stack to determine if the card can be dropped or not.

Drag and Drop in Unity Part One

I’m starting to experiment with Unity for a hobby project involving a card game.

In almost any card game, there are certain areas of the table for a collection of cards. Whether this is the vertical stack in Solitaire, the lands in Magic: The Gathering or a hidden hand of cards that aren’t ‘on’ the table, there are different areas that contain groups of cards. These cards will usually need to move between the card containers, so I figured the first thing to do would be to implement dragging and dropping. After watching a few video tutorials (the most useful was this one), I was ready to try some things out.

I started with a 2D game in Unity – card games are famously 2-dimensional. To this, I added a Panel and named it “Container”. Then I added a Panel inside the Container and named it “Item”. I made the Containers a little bigger than the Items so it was easier to see them, and duplicated this until there were 4.

I added a Grid Layout component to all of the “Container” objects and set the Child Alignment to ‘Middle Center’, then added a Constraint of ‘Fixed Row Count’ and the Constraint Count to 1 so that each container would only ever have one row of items.

Each “Item” needed a script – named DragHandler – to handle the dragging behaviour. The script describes how the object should behave when being dragged around, including how to begin dragging it in the first place. To do this it needs to reference the UnityEngine.EventSystems namespace, and implement the three interfaces IBeginDragHandler, IDragHandler and IEndDragHandler.

First of all, it needs some properties and fields set up.

public static GameObject ItemBeingDragged;
private Vector2 _startPosition;
private Transform _startParent;

This is almost verbatim from the tutorial video, and I will probably be refactoring that static GameObject field at some point in the future (it ought to be a property if it’s public).

public void OnBeginDrag(PointerEventData eventData)
{     ItemBeingDragged = gameObject;     _startPosition = transform.position;     _startParent = transform.parent;     GetComponent<CanvasGroup>().blocksRaycasts = false;
}

OnBeginDrag starts the process off by setting the ItemBeingDragged to the current GameObject, saving the start position (so it can be returned later if the drop is unsuccessful) and saving the original parent (same reason). Finally it finds the CanvasGroup component and stops blocking raycasts so that events can be fired through the item being dragged (this will be needed to drop it).

public void OnDrag(PointerEventData eventData)
{     transform.position = Input.mousePosition;
}

OnDrag just makes the dragged item follow the mouse cursor until it is dropped.

public void OnEndDrag(PointerEventData eventData)
{     ItemBeingDragged = null;     if (_startParent == transform.parent)     {         transform.position = _startPosition;     }     GetComponent<CanvasGroup>().blocksRaycasts = true; 
}

Finally, when the drag is over, the ItemBeingDragged is forgotten (set to null), and if the parent item at the end is the same as the beginning it is returned to it’s starting position. Finally, raycast blocking is restored so it can pick up further events.

The Container objects need a script too, which will also need to reference UnityEngine.EventSystems and implement an interface. This time, IDropHandler so we can drop items there.

public void OnDrop(PointerEventData eventData)
{     DragHandler.ItemBeingDragged.transform.SetParent(transform);
}

This is a simple script. It references the static ItemBeingDragged property from the DragHandler script, and sets its parent to whatever the drop target is.

With all this set up, I can drag Item panels from one Container to another. Or move a card from one location to another, eventually.