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:
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:
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.
Rly thanks :)
You saved me a lot of work and time, really thank you very much =)
how i can make it smooth?
If you mean inertia, I have answered that before.
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
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.
Is there a way to zoom the view with the mousewheel too at the mouse xy like the room editor?
I plan to make example on that in the future.
Very useful code, thanks friend!! =D
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 :)
Perfect! Very usefull, thanks.
Thank you for this! I couldn’t find it anywhere else. You saved me :D
Wow this is so cool, its spot on, so on point….keep it up bro
Thank you for the helpful tutorial!
quick, well written thorough,
keep on truckin’
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?
Yeah, I have the view following the player, is there a way to disable that temporarily?
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.
Sweet, thanks a lot! I will try that.
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
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.
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
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.
Very simple yet useful script, implemented it in my strategy game and gave credit :)