GameMaker: Click ‘n drag to pan view

vis-view-drag

Today I'm going to tell a bit about implementing "click and drag to scroll" type of effect in GameMaker. This particular thing is useful for strategy games (and normally bound to middle mouse button), applications (where visible area may exceed available window space), and various mobile games and applications (where visible area may be panned by tapping and dragging the finger).
Effect itself looks like this:

Animated demonstration for a tutorial on making click-and-drag views in GameMaker

Implementation

Implementation is fairly straight-forward:

First, let there be a variable to indicate that view is currently being dragged. This goes into Create event:

dragging = false

Then there'll have to be an event to start the process of dragging view around. That would be either Global Mouse Left Pressed or Global Mouse Middle Pressed:

dragging = true
drag_x = mouse_x
drag_y = mouse_y

drag_x and drag_y do not do much yet, but these are going to indicate the "starting point" from which view movement begins. I'll explain this in a bit.

Perhaps the most important part about making this work is the "update" code. This goes into any of three Step events:

if (dragging) {
    // actual dragging logic:
    view_xview = view_xview + drag_x - mouse_x
    view_yview = view_yview + drag_y - mouse_y
}

Here we check if view is being dragged, and if that's so, change it's position accordingly. Formula is simple but confusing - new view position is calculated by adding difference between starting mouse coordinates and current mouse coordinates to current view position. This works because mouse_x and mouse_y already include view coordinates in their value. So actually the formula works like this behind the scences (_mouse_* implying relative mouse position to view or dragging offset accordingly):

    view_xview = drag_xview + drag_mouse_x + view_xview - view_xview - view_mouse_x
    view_yview = drag_yview + drag_mouse_y + view_yview - view_yview - view_mouse_y

After reducing the self-destructing view_xview - view_xview, formula becomes quite simpler:

    view_xview = drag_xview + drag_mouse_x - view_mouse_x
    view_yview = drag_yview + drag_mouse_y - view_mouse_y

So, essentially, we are pretty much adding difference between old and new mouse positions in the view to original view's position. Swapping signs of mouse coordinates (+ mouse_x - drag_x) would have granted us effect when scrolling moves view in opposite direction. Which may be handy as well, but in other situations.

Now that most of things are done, there's the last but not least important thing to add - stopping the drag. This goes into Release variant of event that you've added start of process to.

dragging = false

Useful extras

Staying inside

By default, with above script view can be dragged around anywhere, giving player the freedom to leave the room and go exploring the empty nothingness beyond the seen limits.
Fortunately, this is fixable at expense of adding two lines of code into update:

if (dragging) {
    // actual dragging logic:
    view_xview = view_xview + drag_x - mouse_x
    view_yview = view_yview + drag_y - mouse_y
    // make sure view doesn't go outside the room:
    view_xview = max(0, min(view_xview, room_width - view_wview))
    view_yview = max(0, min(view_yview, room_height - view_hview))
}

Monoblock

In preference of some, and for use as a script, all event codes can be joined into one, going into Step event:

// start:
if (mouse_check_button_pressed(mb_left)) {
    drag_x = mouse_x
    drag_y = mouse_y
}
// update:
if (mouse_check_button(mb_left)) {
    // actual dragging logic:
    view_xview = drag_x - (mouse_x - view_xview)
    view_yview = drag_y - (mouse_y - view_yview)
    // make sure view doesn't go outside the room:
    view_xview = max(0, min(view_xview, room_width - view_wview))
    view_yview = max(0, min(view_yview, room_height - view_hview))
}

Downloads

For purposes of easier observation, or discovering whether anything was hidden beyond the seen area, you can download an example that was used for illustrations here. Both multi-event and single-event implementations are included:

Download GMK

Related posts:

29 thoughts on “GameMaker: Click ‘n drag to pan view

  1. This might seem really obvious – but remember to check “Enable the use of views” in your room settings (under the Views tab). Without this, your mouse drag will move things too quickly.

  2. how would I add an object on screen that i can drag around that also moves with the view when dragging it is done? In the objects step:

    if global.drag == true
    {
    x = mouse_x
    y = mouse_y
    }
    if global.drag == false
    {
    x = view_xview + x
    y = view_yview + y
    }

    Have any ideas getting this to work?

    • I’m not entirely sure what you are trying to accomplish – if you want the object to always be fixed at a position, you’d do something like

      x = view_xview + xstart;
      y = view_yview + ystart;

      since doing the way shown would just rapidly accelerate object past screen bounds as soon as view is moved (as you are basically using view coordinates as object speed).

      Perhaps readjust xstart\ystart (or make separate variables for this) if you want the object to be on-screen but draggable.

      • thanks so much for your response. Im basically trying to move an object around the view with the mouse button. But it fails to stay in position within the view once ive finished moving it.

        x = view_xview + xpos
        y = view_yview + ypos

        if global.move_obj == true
        {
        xpos = mouse_x
        ypos = mouse_y
        }

        ive taken what youve said into mind but am still having some trouble :S
        this launches the object off screen the higher the x and y values are.

  3. Thanks!
    I have no idea why I was updating draw_x and draw_y with new mouse values each step, in my own code. Now it’s working how it’s supposed to :)

  4. Hey, thanks for that.

    Is there a way to have the view not snap back to start when mouse button is released? I need to be able to scroll the view, and have it stop, then gain control over the mouse again to move objects around etc.

    • That does not seem to happen in the example. Do you have built-in view following enabled for some object in your game, perhaps?

        • Yes, you can set `view_object` to `noone` to stop the built-in following behaviour, and later set it back to `obj_player` (or whatever the name is in your case) to resume it.

  5. In these two lines, is there a reason you are not using the clamp function?
    view_xview = max(0, min(view_xview, room_width – view_wview))
    view_yview = max(0, min(view_yview, room_height – view_hview))

    Like this:
    view_xview = clamp(view_xview, 0, room_width – view_wview);
    view_yview = clamp(view_yview, 0, room_height – view_yview);

    I would think that the single function call simplifies the code a little and possibly runs a little faster, since there is no nested function calls.

    • clamp only exists in GameMaker: Studio and in later updates of GameMaker 8.1. Since most examples on my blog are intended to work in 8.0, 8.1, and Studio, current approach is used.
      In GameMaker 8.0 you can technically also use median instead of clamp, but I’m not sure, whether it’s less, or more confusing, than a min-max pair (which is pretty standard).
      On the performance… while a valid point, view dragging code is not really a good place to seek micro-optimizations in, as it is executed once per game step at best.

      • Hi, I found this article very useful… but i’d like to ask you a new question:
        Is there any way to have any inertia scrolling after the mouse is released?
        I don’t like the way the view scroll suddenly stops after I release the mouse button…
        Thanx in advance

        • You could store the distance moved per frame, like

          delta_x = drag_x - (mouse_x - view_xview) - view_xview
          view_xview += delta_x

          and then translate the last known delta into velocity when the mouse is released. Such “velocity” would be implemented as a simple pair of variables that are added to view x/y each step and reduced (“friction”) afterwards.

  6. Thankyou so much for this! Could not find this info anywhere!
    For anyone using love2d this is how I added it, with using the camera from here http://nova-fusion.com/2011/04/19/cameras-in-love2d-part-1-the-basics/. Not sure if there is a better way as I’m fairly new to it, but it works… (map.width and map.height are the size of map)

    function love.update(dt)
    if love.mouse.isDown("l") then
    dragging(true, drag_x, drag_y)
    end
    end

    function dragging(mouseDown, drag_x, drag_y)
    if mouseDown == true then
    --dragging logic
    local x, y = camera:mousePosition()
    camera._x = camera._x + drag_x - x
    camera._y = camera._y + drag_y - y
    --boundaries
    camera._x = math.max(0, math.min(camera._x, map.width - love.graphics.getWidth()))
    camera._y = math.max(0, math.min(camera._y, map.height - love.graphics.getHeight()))
    end
    end

    function love.mousepressed(x, y, button)
    if button == 'l' then
    drag_x, drag_y = camera:mousePosition()
    end
    end

    function love.mousereleased( x, y, button )
    if button == 'l' then
    mouseDown = false
    end
    end

  7. Hi there! I’ve noticed your blog has russian! Is it your own decision (and are YOU RUSSIAN) or just a default thing?

    • Own decision. You can kind of tell from my About page – “I live in Ukraine […] I speak Ukrainian, Russian, and English freely.”.

      And yes, this means many posts are written twice.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.