Skip to content

Drag & Drop module

ivan-mogilko edited this page Jul 20, 2022 · 15 revisions

DragDrop and DragDropCommon are two modules that make it possible to drag things around in AGS. What is the difference between these two?

DragDrop module implements an abstract drag-and-drop functionality. This means that the module does not drag any actual game object on its own, but rather calculates the dragging and dropping action, and tells you what and how happens, so that you could script the actual object movement. You might say, this module provides an idea of dragging, rather than dragging itself :). The upside of such approach is that you may script dragging of anything, both standard AGS objects and your own custom things, even non-ordinary pseudo-objects. For example, you may use this module to script dragging a line from point A to B, or draw a rectangle by dragging one of it's corners. The downside is that you have to be ready to do some extra work, and understand scripting well.

When you need to just make normal ordinary game objects dragged around in your game, this is what DragDropCommon module is for. DragDropCommon supports drag-and-drop for Characters, Room Objects, GUIs and GUI controls, and inventory items, right out of the box, only with a minimal setup.

NOTE: DragDropCommon module actually depends on DragDrop module, so you will have to add both to your game if you want to use DragDropCommon.

Quick example

Just to demonstrate the use of these modules, here's the quick example:

  1. Add both DragDrop and DragDropCommon modules to your game. DragDrop should be higher in the module list, or your game won't compile.
  2. Go to the room editor, and add "After Fade-in" event handler. In script put following code:
    function room_AfterFadeIn() {
        DragDrop.Enabled = true;
        DragDropCommon.ModeEnabled[eDragDropCharacter] = true;
        DragDropCommon.ModeEnabled[eDragDropRoomObject] = true;
        DragDropCommon.DragMove = eDDCmnMoveSelf;
    }
  1. Just in case, add following code to the "Leaves room" event:
    function room_Leave() {
        DragDropCommon.DisableAllModes();
        DragDrop.Enabled = false;
    }
  1. That's it! Now you can drag and drop characters and objects with your mouse in that room.

Basic terms

Term Meaning
Draggable object This is something that can be dragged to another position.
Hooking Is an event of "grappling up" the draggable object to move it somewhere.
Hook Key Is the key or mouse button that should be pressed down to hook an object.
Cancel The event of "unhooking" an object before the dragging even started.
Dragging Is the process of moving hooked object around while the hook key is down; NOTE: it does not have to literally move an actual object on screen, it may be a graphical copy, or a "pseudo-object", and so on.
Drop The event of putting an object onto new position; usually occurs when the hook key was released, but may also be triggered by script command.
Revert The event of returning an object to the old position; for example may be scripted to happen if the drop position is invalid.

The drag-and-drop action consists of several steps or phases. In a nutshell it looks like this:

Step Meaning
Idle First, we wait for a player to press a Hook Key down.
Hooking As soon as the key is pressed, we look for a suitable object under the mouse cursor. If failed, we return to Idle.
Waiting for drag After the object got "hooked", we're waiting for drag conditions to match, such as minimal mouse move distance or a time the hook key have been held down.
Cancel If the hook key was released before the drag conditions are met, the drag is cancelled and the hooked object remains in place. Then we return to Idle.
Dragging The object is being dragged around. It takes releasing hook key or explicit command for a dragging to stop.
Dropping If the hook key was released while dragging the object, we're checking if the new position is suitable for placing the object or not. If not, then the object will be returned to the old position (reverted).
After dropped This is one extra game tick before returning to Idle mode, at this point we know the result of the drop (drop or revert).

Using DragDropCommon

DragDropCommon module is meant for dragging the built-in AGS object types, such as Characters, Room Objects, GUIs and GUI controls, and Inventory Items. It is pretty simple to setup and use. Internally this module uses DragDrop module, so you'll have to install one into your game as well. (Please, remember that DragDrop module has to be positioned above DragDropCommon in the list of scripts, otherwise your game won't compile properly.)

Setting up

This is how you configure the module up.

static bool DragDrop.Enabled
this attribute belongs to DragDrop module, but DragDropCommon won't work without it, so it has to be set too. DragDrop.Enabled = true; turns everything on, and DragDrop.Enabled = false; disables everything. You may switch it anytime. Disabling this in the middle of the dragging action will reset everything at the following game update.

static bool DragDropCommon.ModeEnabled[]
this indexed (array) attribute is used to get or set which types of game objects are enabled for auto-dragging. These modes are described by a enum DragDropCommonMode:

  • eDragDropCharacter
  • eDragDropGUI
  • eDragDropGUIControl
  • eDragDropRoomObject
  • eDragDropInvItem

For example: DragDropCommon.ModeEnabled[eDragDropCharacter] = true; enables automatic hookup and dragging of Characters. You may enable multiple modes at once. If you don't enable any, there's still a way to hook up an object manually using one of the "TryHook..." functions (see below).

static void DragDropCommon.DisableAllModes()
disables everything at once. This is simply a convenience function.

static bool DragDropCommon.PixelPerfect
gets/sets whether click on AGS object should be tested using pixel-perfect detection; otherwise uses a simple bounding rectangle test. NOTE: currently only works for Characters and Room Objects.

static bool DragDropCommon.TestClickable
gets/sets whether only clickable objects should be dragged.

static DragDropCommonMove DragDropCommon.DragMove
gets/sets the dragging representation mode. These modes are described by a enum DragDropCommonMove:

Mode Description
eDDCmnMoveSelf Drag actual object itself (position updates real-time); NOTE: cannot be used with inventory items.
eDDCmnMoveGhostOverlay Create and drag overlay with object's image, while object stays in place until drag ends; NOTE: currently only works for Characters and Room Objects.
eDDCmnMoveGhostGUI Drag GUI with object's image, you must provide the GUI yourself; NOTE: this is currently the only way to drag inventory items.

static GUI* DragDropCommon.GhostGUI
gets/sets the GUI for eDDCmnMoveGhostGUI mode. You must set this if you're using eDDCmnMoveGhostGUI mode, otherwise the module will crash with "null pointer" error.

static bool DragDropCommon.GhostAlpha
gets/sets whether the "ghost" representation should keep sprite's alpha channel (default = true).

static int DragDropCommon.GhostTransparency
gets/sets the transparency of a "ghost overlay" or "ghost GUI" representation (default = 33).

State control

Following attributes and functions tell and control the dragging state:

static readonly Character* DragDropCommon._Character
returns the currently hooked Character, or null if none is hooked.

static readonly GUI* DragDropCommon._GUI
returns the currently hooked GUI, or null if none is hooked.

static readonly GUIControl* DragDropCommon._GUIControl
returns the currently hooked GUI control, or null if none is hooked.

static readonly Object* DragDropCommon._RoomObject
returns the currently hooked Room Object, or null if none is hooked.

static readonly InventoryItem* DragDropCommon._InvItem
returns the currently hooked Inventory Item, or null if none is hooked.

static readonly int DragDropCommon.ObjectWidth
returns the hooked object's width, or the width of its representation.

static readonly int DragDropCommon.ObjectHeight
returns the hooked object's height, or the width of its representation.

static readonly int DragDropCommon.UsedGhostGraphic
returns the dragged object's representation graphic, if one of the "ghost" modes are used.

static bool DragDropCommon.TryHookCharacter()
when DragDrop is in "Hooking object" state, this will try to hook up a Character under the mouse cursor.

static bool DragDropCommon.TryHookGUI()
when DragDrop is in "Hooking object" state, this will try to hook up a GUI under the mouse cursor.

static bool DragDropCommon.TryHookGUIControl()
when DragDrop is in "Hooking object" state, this will try to hook up a GUI control under the mouse cursor.

static bool DragDropCommon.TryHookRoomObject()
when DragDrop is in "Hooking object" state, this will try to hook up a Room Object under the mouse cursor.

static bool DragDropCommon.TryHookInventoryItem()
when DragDrop is in "Hooking object" state, this will try to hook up a Inventory Item under the mouse cursor.

static bool DragDropCommon.TryHookDraggableObject()
when DragDrop is in "Hooking object" state, this will try to hook up any suitable object under the mouse cursor, depending on which types are enabled using DragDropCommon.ModeEnabled[].

What is next?

In a most trivial case all you need to do is to configure DragDropCommon's options and enable wanted modes. After that the player will be able to move objects around freely in your game. You may enable and disable the dragging and change modes when the player enters and leaves the room, or any time really. (See the Quick Example again.)

However, usually it's not enough. You might want to know which objects are dragged and where are they dropped, and for that simply tracking object's location may not be enough; you might also want to detect some events.

Often there are other conditions or restrictions that you want to apply. Like, allow to drag only certain objects, or allow to drop objects only in particular locations.

This is where you have to go deeper and learn how to use the base DragDrop module.

Using DragDrop

Like was said earlier, DragDrop module itself provides an abstract functionality, where it only tests for the player input's and tells you what should happen each moment in time, while it is up to you to handle such events correspondingly: provide description of a draggable object under the cursor, update the object's position as it is being dragged around, and so on.

DragDrop works in two modes: automatic and manual, where the automatic mode means that it listens to player's input itself, and in the manual mode you may send input commands to DragDrop as you see fit. These modes may also be mixed, where one part is done automatically and another is controlled by you. This is explained in more detail below.

But before we get to that, we need to quickly discuss how the module's events are handled.

Signal properties

Since AGS does not support custom callbacks (that is: calling your own functions when something happened in script module), DragDrop tells you about immediate events by setting certain "signal" properties. These properties are easily distinguished by Evt prefix. For example, a property may be called EvtDragStarted, which tells that an object is begun being dragged. A signal property may have a value of either 1 or 0 (or true and false). The positive value is only set for 1 game tick (update), and is reset immediately afterwards. They mean that the event has just occured, and you should react to that. The next tick they will be reset until some event occurs again.

You will have to check these properties periodically, in the "repeatedly_execute" or "repeatedly_execute_always" functions, so to know when the related events occur.

For example:

function repeatedly_execute_always() {
    if (DragDrop.EvtDragStarted) {
        myLabel.Text = "Started dragging something!";
    } else if (DragDrop.EvtDropped) {
        myLabel.Text = "Something was dropped.";
    }
}

DragDrop steps and basic controls

Now let's discuss automation and manual override.

Previously we mentioned the steps that a drag-n-drop process takes. These steps may be advanced automatically or manually. Automatic execution requires certain parameters to be set for DragDrop, while manual actions require you to call certain functions. Following table allows a quick glance on which properties and functions relate to which steps:

  • Event - is a signal property that tells when DragDrop enters this step.
  • Automation - is a list of options that configure DragDrop's automatic behavior during this step.
  • Control - is a list of functions that allow user to advance from this sto a different step.
Step Event Automation Control
Idle none DefaultHookKey, DefaultHookMouseButton, AutoTrackHookKey HookKeyDown()
Hooking EvtWantObject none HookObject()
Waiting for drag EvtObjectHooked DragMinDistance, DragMinTime HookKeyUp(), Drop(), DropAt(), Revert()
Cancel EvtCancelled none none
Dragging EvtDragStarted AutoTrackHookKey HookKeyUp(), Drop(), DropAt(), Revert()
Dropping EvtWantDrop DefaultUnhookAction Drop(), DropAt(), Revert()
After dropped EvtDropped none none

So, how does this work in practice? Suppose you'd like DragDrop react to certain mouse button, hook and unhook (drop) automatically, and drag Room Objects. Then you set it up like this:

function game_start() {
    DragDrop.Enabled = true;
    DragDrop.DefaultHookMouseButton = eMouseLeft;
    DragDrop.AutoTrackHookKey = true;
    DragDrop.DefaultUnhookAction = eDDUnhookDrop;
}

But because DragDrop knows nothing about types of objects, you have to provide their arbitrary description, and tell when it hooked one. In return, DragDrop will tell you when the dragging is active and where the current object's position is supposed to be; then you may use this information to actually reposition the object. These are the only two things that must be done manually, even if using default hook/unhook actions (and unless you're using DragDropCommon, which already does all that for you):

function repeatedly_execute_always() {
    if (DragDrop.EvtWantObject) {
        Object* obj = Object.GetAtScreenXY(mouse.x, mouse.y);
        if (obj != null) {
            // Hook up this object, under custom mode "1", saving its starting coordinates
            // and object's ID, which may be later used to find out what we are dragging
            DragDrop.HookObject(1, obj.X, obj.Y, obj.ID);
        }
    }
    else if (DragDrop.IsDragging) {
        // remember we saved object's ID in HookObject?
        // we're going to use that now --
        int objid = DragDrop.ObjectTag;
        // DragDrop calculates what the new object position would be,
        // comparing its starting position and relative mouse movement
        object[objid].X = DragDrop.ObjectX;
        object[objid].Y = DragDrop.ObjectY;
    }
}

In the above example everything is automated except of actual object's hookup and repositioning.

Now, let's say you want to keep it like that, but control whether the object is dropped or reverted in realtime. For that you'd have to additionally track EvtWantDrop event and call either Drop() or Revert() before DragDrop does its default action:

function repeatedly_execute_always() {
    //
    // All the previous code goes here
    //
    
    if (DragDrop.EvtWantDrop) {
        // For example, let's make it drop normally only on the walkable area,
        // and revert otherwise.
        if (GetWalkableAreaAt(DragDrop.ObjectX, DragDrop.ObjectY) > 0) {
            DragDrop.Drop(); // let it drop on the new place
        } else {
            DragDrop.Revert(); // return it back to the old place
        }
    } else if (DragDrop.EvtDropped) {
        // Adjust the final position, which will be starting pos if reverted
        int objid = DragDrop.ObjectTag;
        object[objid].X = DragDrop.ObjectX;
        object[objid].Y = DragDrop.ObjectY;
    }
}

Thus, step by step, you can replace automatic behavior for a manual one.

For a more complex scenario, let's say you want to control hooking and unhooking yourself, without using default key action.

function game_start() {
    DragDrop.Enabled = true;
    DragDrop.AutoTrackHookKey = false;
}

function repeatedly_execute_always() {
    if (DragDrop.IsIdle) {
        // Assume we want to use Ctrl + Mouse Click
        if (Mouse.IsButtonDown(eMouseLeft) && IsKeyPressed(eKeyCtrlLeft)) {
            // We don't really have to tell a real key here if AutoTrackHookKey = false;
            // but this information is also saved *for you*
            DragDrop.HookKeyDown(0, eMouseLeft);
        }
    }
    
    //
    // All the previous code goes here
    //
    
    // Additionally we now should test if our keys are still pressed
    if (DragDrop.IsDragging) {
        if (!Mouse.IsButtonDown(eMouseLeft) || !IsKeyPressed(eKeyCtrlLeft)) {
            // Tell DragDrop that the hook key has been released
            DragDrop.HookKeyUp();
        }
    }
}

At this point you may begin to guess, that in the manual mode DragDrop does not care about real keys and therefore your hooking condition could be not related to keys or mouse clicks at all. For this module the "object", the "hook key", and most other things, are merely abstractions. The HookObject may be called for an imaginary object. The HookKeyDown and HookKeyUp may be triggered by certain game events, or by timer, anything really.

The only real thing in DragDrop currently is mouse position, but it's likely to be changed in a future version, to allow to have an abstract cursor position too.

Above was the introduction to the DragDrop automation and manual control. Below is the full list of the module's properties and functions.

Setting up

This is how you configure the module up.

static bool DragDrop.Enabled
enables or disables DragDrop work, without cancelling any other settings. Disabling this in the middle of the dragging action will reset everything at the following game update.

static eKeyCode DragDrop.DefaultHookKey
gets/sets default hook key, that is a key which must be pressed to drag an object, and released to drop it. Set DragDrop.DefaultHookKey = 0; if you don't want to use a strictly defined key, but in that case you will have to call DragDrop.HookKeyDown yourself.

static MouseButton DragDrop.DefaultHookMouseButton
gets/sets default hook mouse button, that is a button which must be pressed to drag an object, and released to drop it. Set DragDrop.DefaultHookMouseButton = 0; if you don't want to use a strictly defined mouse button, but in that case you will have to call DragDrop.HookKeyDown yourself.

static bool DragDrop.AutoTrackHookKey
gets/sets whether the module should automatically track hook key/button's state (pressed, released). When disabled, you should call DragDrop.HookKeyDown and DragDrop.HookKeyUp yourself.

static int DragDrop.DragMinDistance
gets/sets minimal number of pixels the mouse should move before it is considered to be dragging.

static int DragDrop.DragMinTime
gets/sets minimal time (in milliseconds) the hook key should be pressed down before it is considered to be dragging.

static DragDropUnhookAction DragDrop.DefaultUnhookAction
gets/sets the default action that should be performed when the hook key is released. These actions are described by a enum DragDropUnhookAction:

Action Description
eDDUnhookDrop Drop the dragged object at the last cursor position.
eDDUnhookRevert Revert the dragged object to its original position.

this action may be overriden by calling Drop() or Revert().

Event signals

Following attributes work as event "signals", which you may test in repeatedly_execute or repeatedly_execute_always:

static readonly bool DragDrop.EvtWantObject
gets if the hook key was pushed down and the module is looking for a draggable object under the cursor. This is when the user should supply module with the draggable object.

static readonly bool DragDrop.EvtObjectHooked
gets if the draggable object was just hooked. This is when the user may take some action right before the object will be actually dragged around.

static readonly bool DragDrop.EvtDragStarted
gets if the drag just started. This event takes place if the object was kept hooked until the drag conditions were met (minimal drag time and/or distance).

static readonly bool DragDrop.EvtWantDrop
gets if the drag was just released. The event takes place just a tick before the object is actually dropped on a new place, letting user to change drop coordinates by calling DropAt or revert the drag with Revert.

static readonly bool DragDrop.EvtDropped
gets if the object was just dropped on a new position (or reverted). At this point the module still has the saved object's description, drag parameters and last object's coordinates.

static readonly bool DragDrop.EvtCancelled
gets if the object was unhooked before the drag even commenced.

State control

Following attributes and functions tell and control the dragging state:

static readonly bool DragDrop.IsIdle
gets if the module is currently waiting for action.

static readonly bool DragDrop.HasObjectHooked
gets if the module has an object hooked (before dragging, during dragging or at the drop state).

static readonly bool DragDrop.IsDragging
gets if the object is currently being dragged.

static readonly int DragDrop.CurrentKey
gets the key used to hook the current object. May be an imaginary key code. Returns 0 if no object was hooked.

static readonly MouseButton DragDrop.CurrentMouseButton
gets the mouse button used to hook the current object. May be an imaginary mouse button. Returns 0 if no object was hooked.

static readonly int DragDrop.CurrentMode
gets the user-defined drag mode, as set by HookObject call. Returns 0 if no object was hooked.

static readonly int DragDrop.ObjectTag;
gets the user-defined integer tag, as set by HookObject call. Returns 0 if no object was hooked.

static readonly String DragDrop.ObjectSTag
gets the user-defined String tag, as set by HookObject call. Returns 0 if no object was hooked.

static readonly int DragDrop.DragStartX
gets X coordinate of cursor position at which the hook key was pushed down.

static readonly int DragDrop.DragStartY
gets Y coordinate of cursor position at which the hook key was pushed down.

static readonly int DragDrop.ObjectStartX
gets X coordinate of initial object's position.

static readonly int DragDrop.ObjectStartY
gets Y coordinate of initial object's position.

static readonly int DragDrop.ObjectX
gets X coordinate of the current dragged object's position.

static readonly int DragDrop.ObjectY
gets Y coordinate of the current dragged object's position.

static void DragDrop.HookKeyDown(optional int user_key, optional MouseButton mbtn)
notifies a hook key push down. This does not have to be a real keycode, but in that case the module won't be able to automatically track its release.

static void DragDrop.HookKeyUp()
notifies a hook key release. If an object was being dragged, it will be dropped. If an object was hooked up, but not dragged yet, drag will be cancelled.

static void DragDrop.HookObject(int user_mode, int obj_x, int obj_y, optional int tag, optional String stag)
assigns a draggable object for the module. The module will save all parameters and provide them for reading back until drag ends or is cancelled. Starting object coordinates obj_x and obj_y will be used to calculate the relative distance between current cursor coordinates and the object, so that when the cursor moves the object's position will change accordingly.

static void DragDrop.Drop()
immediately drops the object. Does nothing if no object was hooked up.

static void DragDrop.DropAt(int x, int y)
immediately drops the object at the given coordinates. Does nothing if no object was hooked up.

static void DragDrop.Revert()
immediately resets the object to the original position. Does nothing if no object was hooked up.

Clone this wiki locally