Drag me, baby!

Image credit: Slack emoji

Drag me, baby!

Hi guys! Thanks for stopping by. Firstly, I’d like to thank for all of your kind words about my blog. I’m really surprised that people actually read it. Even before my pole dance class I had the opportunity to talk about programming… ;D Unfortunately, today’s post will strictly concern my project and a little piece of code that I recently had to write. So if you’re not a Unity programmer, it probably won’t be interesting for you. :(

But if you are a Unity programmer, please point out all the things that you would change in that code. Let’s get started!

Looking for inspiration

I want my tool to be maximally convenient to use for game designers. I figured out that the best way of building logical expressions would be to literally assemble them from previously predefined entities and attributes. For the user to freely maneuver the elements I needed to implement a drag and drop mechanism.

For a quick start I wanted to just google first looking-like-it-should-work solution to get the names of all useful event handlers and perhaps event adapt it in the project. My first shot was this tutorial. It was pretty appealing and I was watching it with pleasure, but suddenly my heart froze – I saw this piece of code:

void Update()
{
    Vector3 posi = (Input.mousePosition - GameObject
            .FindGameObjectWithTag("Canvas")
            .GetComponent<RectTransform>()
            .localPosition);
    draggedItemGameObject.GetComponent<RectTransform>()
            .localPosition = new Vector3(posi.x + 15, posi.y - 15, posi.z);
}

Yea – finding a game object by tag and getting a component twice in an update function. I know it is only for the time of dragging but still, it is a very nasty practice. And that sweet little “15”. :) Additionally, this solution was strongly adjusted for inventory implementation purpose, which was too much for me. I lost my faith in youtube on that matter – I know there are better tutorials on this topic for sure, I just didn’t have the patience to look for them.

I decided to head out straight to the root – Unity documentation, which I deeply hate. I still remember struggling with new networking system on Slavic Game Jam, when there were some doc pages simply left blank. I saw people on the forums guessing how some things might be working, because there was no viable source of information. Ah, never mind.

Here I found a complete example of a drag and drop script. Yay, wow, wow, let’s copy paste it all!

1

It wasn’t even compiling. :]

RectTransformUtility.ScreenPointToWorldPointInRectangle - apparently this method has changed and no longer accepts arguments in the example. Still, it looked quite well, so I decided to base on it.

My little script

This is my result. The idea is that it is just a base class for some draggable items, which can be placed in some container which derives from an ItemSlot class.

The first thing I did was to extract this useful FindInParents method as an extension method for game objects. I think it might be useful in other parts of the project too.

Secondly, I moved the moment of an icon creation to the OnEnable method, to avoid unnecessarily invoking it multiple times. This is also the moment when I look for the container for my dragged item.

void OnEnable()
{
    CreateDraggingIcon();
    _draggingIcon.Hide();
    var canvas = gameObject.FindInParents<ItemSlot>();
    if(canvas != null) {
        _itemsContainer = canvas.GetComponent<ItemSlot>();
    }
}

I’m not sure what “dragOnSurfaces” was supposed to mean, so after cutting out some more code my OnBeginDrag method looks nice and clean. OnDrag method looks almost exactly the same as in docs.

public virtual void OnBeginDrag(PointerEventData eventData)
{
    _draggingIcon.Show();
    SetDraggedPosition(eventData);
}

public virtual void OnDrag(PointerEventData data)
{
    SetDraggedPosition(data);
}

The OnEndDrag method now causes the ItemSlot to attach this item to itself. Perhaps in most of use cases this attaching will simply cause and item to take its place, but in my implementation there is some additional work to be done.

public virtual void OnEndDrag(PointerEventData eventData)
{
    if(_itemsContainer != null) {
        _itemsContainer.AttachItem(gameObject);
    }
    _draggingIcon.Hide();
}

I didn’t stick with the assumption in documentation, that the icon would always be the image extracted from the element. I can think of many cases when the icon creation will have more sophisticated logic behind it. So I created the virtual method that simply returns a predefined placeholder and can be easily overridden.

protected virtual GameObject GetDraggingIcon()
{
    return Instantiate(DraggingIconPlaceholder);
}

The moment of an icon creation reduces to finding the canvas to attach our dragged icon, creating the icon object and pinning it to the canvas.

private void CreateDraggingIcon()
{
    var canvas = gameObject.FindInParents<Canvas>();
    if (canvas == null) {
        return;
    }
    _draggingIcon = GetDraggingIcon();
    _draggingIcon.transform.SetParent(canvas.transform, false);
    _draggingIcon.transform.SetAsLastSibling();
}

Since setting the new position of the icon will be invoked many many times, I decided to cache the icon’s transform.

private void SetDraggedPosition(PointerEventData data)
{
    GetCachedIconTransform().position = 
            data.pointerCurrentRaycast.screenPosition;
}

private Transform GetCachedIconTransform()
{
    if(_draggingIconTransform == null && _draggingIcon != null) {
        _draggingIconTransform = _draggingIcon.transform;
    }
    return _draggingIconTransform;
}

I’ve just realized that there is some null-checking missing. Well, let’s just hope the icon will never be null! (◑◡◑)

And that’s it. That is the first piece of code that I’ve written for my master thesis project. Not impressive at all, but Rome wasn’t built in a day, was it?

Oh, and in the last post I mentioned that recently I haven’t experienced any Unity crashes. Ekhm…

1

comments powered by Disqus