Extend Unity’s EventSystem: Drag & Drop

In the last post I gave a quick overview over the new event system that was introduced with Unity 4.6. Following up on those basics I’d like to give you some insights about the customizations I made to fully utilize the possibilities of the event system in our current project. The first big change I made was adding a generic Drag & Drop system by using an own input module derived from the existing PointerInputModule.

General workflow

Before diving into the implementation details, let’s get a rough idea how a generic drag & drop operation should work.

The following steps should be performed and be able to customize for specific projects:

  • Begin Drag
    • Get custom drag data from the dragged object
  • Continue Drag
    • Check, if a drop would be successful currently
    • Inform dragged object if the drop would be successful (e.g. for a visual effect)
  • End Drag
    • Perform drop operation if over a valid drop object
    • Inform dragged object about success of operation (e.g. for a visual effect)

This should make up a pretty solid drag & drop framework already, especially with letting the dragged object know about the current state of the operation. This way nice user feedback is possible.

Extending the input module

The current state of the drag & drop operation has to be stored somewhere. This is where I extended Unity’s PointerInputModule  as there is some logic (starting the operation, updating the success of the operation during the drag) that has to be performed during every drag operation.

For the handlers though, only the ability to get the current drag & drop operation is required, so I added a small interface that the handlers work with instead of the concrete input module:

The drag & drop operation itself looks like this:

To store the drag & drop operations, the ExtPointerInputModule as I called it, contains a dictionary from the pointer id to the operation object:

Start of operation

Unfortunately there is no virtual callback in the PointerInputModule, so we have to check on our own if a drag & drop operation should be started:

The ShouldStartDrag method was just copied from the PointerInputModule as it is private:

During the operation

The input module has another task: To keep the IsSuccessful flag of the drag & drop operation up-to-date during the drag:

What it does it to search for the IDropHandler interface on the current hovered game object. If it finds one it updates the drag & drop operation object and either performs the DragOver event of the new drop object or sets the flag to false if no valid drop object is hovered.

New event: DragOver

The drag events and the drop events are already covered by Unity, so we just need one new event which is sent to a game object. When a game object that has a IDropHandler is entered while doing a drag & drop operation, its DragOver event is called.

To add a new event, we can check how it is done with the existing events in the ExecuteEvents class. We need the following parts:

  • Execute method that calls the correct method on a handler and casts the event data
  • Property or field that holds a pointer to the execute method
  • Setting the pointer on initialization

This way we can use the already existing ExecuteEvents.Execute  method. The easiest way for now is to add the code as static members to the input module:

The interface IDragOverHandler can be used exactly as all the basic Unity handlers by letting a mono behaviour implement it:

Example for drag over handler

A simple example would be to change the color of the drop target when dragged over:

In general though the OnDragOver  method should be used to get the current drag & drop operation and update the IsSuccessful  flag.

Handler for drag object

The dragged object gets already the input events (BeginDrag, Drag, EndDrag) from the basic Unity input module. We can use those events to initialize the drag data and let the dragged object know if the drop will be successful. Therefore I created a general DragOperationHandler that offers Unity events for the special drag & drop operation events:

Starting the operation

The dragged object has to set the data which is dragged in the operation when the drag starts. The StartDragDrop  event is triggered when the drag operation begins. The operation is passed as a parameter, so its Data object can be modified.

During the operation

During the drag & drop operation the dragged object will be informed about changes in the success of the (future) drop operation. The events DropWillBeSuccessful  and DropWillBeUnsuccessful  can be used e.g. to change the visualization of the dragged object when over a valid/invalid drop target.

End of the operation

As we decided at the beginning, the general workflow should also contain informing the dragged object if it was dropped over a valid object. Therefore there are two further events called SuccessfulDragDrop  and UnsuccessfulDragDrop . One of those is called at the end of the drag & drop operation.

Handlers for drop object

A drop target for a drag & drop operation can be defined by adding a generic DropOperationHandler . It contains an event called Drop  which is invoked when a drag operation is ended above it and a DragOver  event that is called when a drag & drop operation is dragged over the object.

If a drag & drop operation is available, it is passed as a parameter. This way the IsSuccessful  flag can be adjusted and the drag data can be used.

Concrete input modules

Before we can put it all together we have to derive a concrete input module from our ExtPointerInputModule  class, e.g. for touch input. The PointerInputModule  is also just a base class and the two concrete modules StandaloneInputModule  and TouchInputModule  are derived from it.

So we added a ExtTouchInputModule  for touch (and fake touch) input:

In general it is not much more as Unity’s TouchInputModule implementation, but uses ExtPointerInputModule as a base class and calls ProcessDragDrop in its process method.

Putting the parts together

Alright, we now have a handler for our drag objects, one for our drop targets and a customized input module that manages the drag operations and calls the new input event DragOver .

 

To assemble a valid drag & drop scene, you should follow those steps:

  • Create a new scene and add an EventSystem
  • Add a ExtTouchInputModule  to the event system game object
    • Enable the ForceModuleActive  checkbox, so fake touch input is used in the editor
  • Add a drag object, e.g. a UI Image, and put a DragOperationHandler  on it
    • You also have to add a custom behaviour that provides a method to set the drag data and link this method to the StartDragDrop  event of the handler
  • Add a drop object, e.g. an Image again, and put a DropOperationHandler  on it
    • You also have to add a custom behaviour that provides methods to handle the DragOver  and Drop  events

The custom behaviours depend on the specific use case, of course, but should be easy enough to implement. Most of the times the DragOver  handler checks if the drag data is of the correct type and may check if the drag data is valid for this specific drop target.

The Drop  handler performs the action for the operation. This can be anything depending on your application.

All the other events are meant to easily add feedback about the drag & drop operation for the user, but can be added bit by bit later.

Adding first feedback

There is one kind of feedback though, that is essential for drag & drop and that is really visually dragging the drag object. The handlers are called even if nothing else happens on the screen and are only affected by the input. So if you start the drag over a drag object and end (release) it over a drop target, the operation is performed. But the user may be a bit confused and can’t be sure that the operation has started already.

So I wrote a script that pulls an object when it is dragged, so it is always positioned at the pointer:

You just have to connect the methods StartPull , ContinuePull  and EndPull  to the drag events and it should work out-of-the-box.

You should also add a CanvasGroup  script, so raycasts on this object are ignored during the time of the drag. Otherwise underlying objects are not hit and the drop target is not recognized.

Conclusion

Phew, this was quite a technical post, hope you could follow my descriptions. I uploaded all the code we talked about to bitbucket, so you can try it on your own.

I plan to add some more blog posts about the topic Drag & Drop. For example how and which user feedback to add during the operation and how we connected it to our Unity asset Data Bind for Unity. So feel free to ask any questions or give me hints on other topics, so I can consider them for my future posts. Any feedback about this post is very welcome as well, of course! 🙂

Update 07.12.16

Due to some comments on this post I decided to have another look into the example as it should also work with 3D objects out of the box. So I added another example to the bitbucket project. There is also a Unity package you can use directly.

The main issue why it didn’t work for some readers was probably that they forgot to add a PhysicsRaycaster to the main camera, so the event system wasn’t able to detect the touched 3D objects. For UI objects the GraphicsRaycaster does this job. Anyway, thanks @Simon and @Jose for reporting the issue! 🙂

  • Well explained! 🙂 One minor addition: In the example script ChangeColorOnDragOver you might want to store the initialColor in OnDragOver instead of Start, in other words, immediately before it is changed. The color might have changed due to other gameplay features after Start, and we would reset it to its original value otherwise.

    Again, nicely done!

    • coeing

      Thanks, makes sense, Nick 🙂

  • nur

    how to drag and drop the object to specific area and in case the object is wrongly place, it will bounce back. can you make the tutorial for it?

    • coeing

      Hi!
      I don’t plan any new posts right now, but I added it to my list of ideas for new posts, so thanks for your feedback 🙂

  • Simon

    Is this only applicable to UI images? I’m trying to get this working with sprites and 3D objects, but it doesn’t seem to be working. I’ve coded all the logic myself, it’d be so much easier to just have an OnBeginDrag() function etc…

    • coeing

      Hi Simon!

      The Drag&Drop system is meant as a general solution, so it should work with sprites and 3D objects as well. You won’t have to touch the basic classes like DragOperationHandler or DropOperationHandler. Instead you add the scripts to your objects and add the logic in custom scripts that you connect via the Unity events on the handlers (see section “Putting the parts together”).

      • Jose

        I agree with Simon, after many tries, this only works with UI not with gameObjects.

        • coeing

          Alright, thanks for reporting. I will see if I have time to look into it again and will try it with 3D objects 🙂

        • coeing

          I added a 3D example 🙂 Thanks for your report!

    • coeing

      I added a 3D example 🙂 Thanks for your report!

  • Don

    Another great article! 😀