GameMaker Tutorials: Flickable Scrolling Gallery

 

In this tutorial we are going to look at creating a flickable, scrolling image gallery as you might find in many games played on mobile devices. This means that the gallery will not only scroll as we drag our finger / mouse across the screen, but that we should be able to flick across the screen and have the gallery scroll quickly in the intended direction. The basic scroll is easy to implement, but getting a decent feeling flick scroll requires a lot more thought. We need to think about how long a flick is, how far on screen was that flick, and then determine how far to scroll as a result. Let’s get to it then!

Download the Work File (.gmz)

Constructing A Gallery

When creating a gallery of images, most people think that it is comprised of images only. While that can work in many situations, it is often better to think of all the Images as being placed on a Page and it is the Page that moves everything around. By separating the two elements it allows us to better maintain the control of the sliding as only one object is being affected directly. All the Images themselves will move, but indirectly by following the Page.

In order to create our gallery, we are going to need three objects: obj_Overlord to spawn everything, obj_Page for controlling the scrolling, and obj_Image that will display the sprite. For this gallery, we have sprites that are all the same size which will simplify our work immensely. We will start by creating a script, scr_GalleryList, with a ds_list to store all the images we want to have in the gallery. For the sake of making our gallery large enough to really test out the scrolling, let’s use each sprite twice.

galleryList = ds_list_create();
ds_list_add(galleryList, spr_Image_01);
ds_list_add(galleryList, spr_Image_02);
ds_list_add(galleryList, spr_Image_03);
ds_list_add(galleryList, spr_Image_04);
ds_list_add(galleryList, spr_Image_05);
ds_list_add(galleryList, spr_Image_06);
ds_list_add(galleryList, spr_Image_07);
ds_list_add(galleryList, spr_Image_08);
ds_list_add(galleryList, spr_Image_01);
ds_list_add(galleryList, spr_Image_02);
ds_list_add(galleryList, spr_Image_03);
ds_list_add(galleryList, spr_Image_04);
ds_list_add(galleryList, spr_Image_05);
ds_list_add(galleryList, spr_Image_06);
ds_list_add(galleryList, spr_Image_07);
ds_list_add(galleryList, spr_Image_08);

We want to run this at the start of the game so place it in the obj_Overlord in a Other | Game Start event. Next, we will need to spawn the Page and all the Images. We can do this at the game start as well, keeping in mind that we need the gallery list first. We create the Page first and then loop through the entire gallery list. After grabbing the image, we get the width of the sprite and multiply it by its position and add a bit of padding (the extra 8 pixels). We are creating a horizontal gallery so each Image needs to be placed beside the one previous to it. We then spawn an Image, assign it a sprite, and give it an offset so it knows where it should be in relation to the Page. Finally we change the offset value of the Page, which we will actually use to determine where the end of the gallery is.

page = instance_create(0, 0, obj_Page);
amountOfImages = ds_list_size(galleryList);
for (i = 0; i < amountOfImages; i++)
{
    theSprite = ds_list_find_value(galleryList, i);
    offsetX = sprite_get_width(theSprite) * i + 8;
    image = instance_create(offsetX, 8, obj_Image);
    image.sprite_index = theSprite;
    image.myOffsetX = offsetX;
    page.myOffsetX = offsetX;
}

Following the Page

Now that the Images have all been spawned we can start on the scrolling movement. The first step here will be to quickly add some code to the Images so that they move with the Page. In a Step event we can have the Image move to the same location as the Page with the appropriate offset. Remember to code defensively and make sure that the Page exists!

if (instance_exists(obj_Page))
{
  x = myOffsetX + obj_Page.x;
}

Now onto the Page and all the scrolling fun. First we are going to use two constants: LEFT and RIGHT, with values of -1 and 1 respectively, just to make the code a bit more readable. We are also going to need a bunch of variables, so lets get those in right away. We will need to know the gallery start and end points, the direction we are sliding, how long the mouse / finger has been down for, and a few booleans for what actions are occurring.

myX = 0;
myOffsetX = 0;
myDir = RIGHT;
currentX = 0;
thePressedTime = 1;
isPressed = false;
hasFlicked = false;
isSliding = false;

Next we will deal with the mouse events. In order to know the difference between a scroll and a flick we need to know how long the player has been holding the mouse down for. A flick is a relatively short amount of time while a scroll would be much longer. To start we need to zero out all of these values when the mouse is clicked. When checking for the mouse position, since we want this to work on mobile devices, make sure you use device_mouse_x. It will work fine on PC builds as well, so why not use it all the time, right?

isPressed = true;
myX = device_mouse_x(0);
thePressedTime = 0;

Upon the release of the button we need to check to see if a flick has occurred. If button has only been pressed for less than a second, which we are using the room speed to judge by, it is a flick. We then determine the total distance we want it to go by subtracting the myX value, which we grabbed when the button was first pressed and the currentX value which we will update every step. To ensure the distance is a positive one we can use the abs function, which we then multiply by the direction (LEFT or RIGHT) to get a final distance value. We then multiply the total disatnce by 8 (this could be any number) so we get a nice long scroll. Finally, we divide that number by the duration of the press. This will allow us to have a balance between a quick flick and a long flick.

isPressed = false;
if (thePressedTime < room_speed && thePressedTime > 1)
{
  hasFlicked = true;
  theDistance = ((abs(myX - currentX)* myDir) * 8) / thePressedTime;
}

Scrolling and Flicking

Now that we have the mouse presses out of the way we can work on the movement. We will start with the Scrolling motion which we will put into its own script. Scrolling starts as soon as the mouse button / finger press has occurred so we need to start tracking the time. We then do a comparison of the previous mouse location against the current location. This allows us to know whether the mouse has moved or not. We also have a 8 pixel buffer in either direction to ensure that the movement is a significant amount. If the direction of the scroll is different than what it previously was, we reset the myX value just in case the motion is supposed to be a flick. We can’t rely on perfect motion when it comes to fingers on a glass screen, now can we! After all of this we apply the scroll motion by subtracting the difference between myX and currentX and divide that by 16 (again, this could be any number) which will give us a nice slow scroll. All of this is then multiplied by the direction so that it moves appropriately.

thePressedTime += 1;

lastX = currentX;
currentX = device_mouse_x(0);
if ( lastX >= currentX + 8 ) 
{ 
    if (myDir == LEFT)
    {
        myDir = RIGHT; 
        myX = currentX;
    }

} 
else if ( lastX <= currentX - 8 ) 
{ 
    if (myDir == RIGHT)
    {
        myDir = LEFT; 
        myX = currentX;
    }
}

x -= (abs(myX - currentX)/16) * myDir;

As for the flick, it is actually a much simpler set of code. If a flick has happened, we move the predetermined distance we have from the release event. We then divide the distance by 1.2 (this should be a realtively small number above 1) which will make the flick slow down over time. If the distance is smaller than two pixels we stop the flick motion. That’s it!

if ( hasFlicked )
{
    x -= theDistance; 
    theDistance = theDistance/1.2;
    if (theDistance < 2 && theDistance > -2) 
    {
        hasFlicked = false;
    }
}

All that we need to do now is to apply these scripts to the Page. In a Step event we just need to check if the button is currently pressed so the according script is run. At the end of this script we also end cap the gallery by checking to see if the Page has gone beyond the start or end points. If it does we set it back to where it should be.

if (isPressed)
{
    scr_Scroll(); 
} 
else 
{
    scr_Flick();
}
if ( x > 8 ) x = 8;
if ( x < -myOffsetX) x = -myOffsetX;

There we go! All we need to do is place a single instance of obj_Overlord into a room and the gallery should work perfectly. If you drag across the screen the images will scroll by horizontally. If you release within a second the gallery should scroll quickly by and slowly come to a stop. If you don’t release at all the gallery will slowly scroll in the intended direction until it gets to the end of the gallery. With a little work you could add multiple rows and columns of images or make the images clickable to display larger images. Now let’s see what you can do with it!

Download the Completed File (.gmz)

If you liked this tutorial and are thinking “Man, I wish this guy would write a book on all this.” Then your wish has come true! 
Check out my book here! If you want to see what you will learn, I have playable versions of the projects here!
HTML5 Game Development with GameMaker

16 thoughts on “GameMaker Tutorials: Flickable Scrolling Gallery

  1. Cool tutorial and I’m definitely going to by your book. One question- how would you make the images clickable?

  2. Glad you liked it!

    As for making the images clickable, it depends on what you want it do. The clicking itself is easy as you just need to add a Mouse Left Pressed event, but what you do with it is where the challenge comes in. For example, if you want the gallery to work as thumbnails and you want to be able to click on the sprite and display a full size image, it’s going to take some retooling. You would first switch from a ds_list to a ds_grid so that you can have an easy way to link the thumbnail and the image (it could be two lists, but that would be prone to bugs). You would also need to create a variable to activate and deactivate the scrolling background when the full size image is displayed.

    Hopefully that points you in the right direction. Maybe I will make a follow up tutorial with this functionality one day 🙂

  3. Jason,

    Love your book and your tutorials.

    I’ve been working on a gallery project and have been using this tutorial as a starting point. An issue I’m having is with the touch interface: I’d like a tap (mb_left) on a thumbnail to take you to the full view of the image (separate room). However, this is firing during a Flick and sometimes even a Scroll. I’ve tried preventing this even from firing if hasFlicked or isSliding are true but sometimes the objImage tap even still fires. It is somewhat inconsistent too.

    What can I do to make sure a click on an Image object is picked up differently than a flick or slide?

    Thanks in advance.

  4. I built a gallery system similar to what you described and will try and dig it out to see exactly what I did. One issue you may have is the changing of the room and returning to the same spot. Do you need to go to another room or would it suffice to just draw a larger image and freeze the background?

  5. Yes it would need to return to the same spot…however —
    I wound up using (and modifying) NailBuster’s ui pack (https://marketplace.yoyogames.com/assets/362/gui-widget-library) off of the YoYo Games marketplace (I’ve used it before). They handle scroll/flick/click a bit differently and it seems to be more sensitive, however, I’ve hit another problem that has me looping back to this tutorial.
    -Unfortunately, what I want to do with this gallery entails loading the thumbnail (and main image) sprites remotely and this is leading to a ton of texture pages. Under Windows and HTML5 running on a desktop, it is holding up, however, when I run it in a browser on an iPad or Phone it breaks down and slows to a grinding halt. I then wrote some code so the remote sprites were only being loaded if they were about to appear (or already were) on screen AND the screen wasn’t scrolling (within NailBuster’s API) but things still break down on a mobile device.

    -Now I’m looping back to your initial example and here’s my plan:
    1) oPage loads one “page” of thumbs (4 images) and displays them in a 2×2 grid on the screen. The image file names are pulled from a separate ds_grid.

    2) A Next/Prev button will pull the next page of thumbs. (Might try a click-drag-flick thing again but this is just easier).

    3) clicking a thumb takes you to a separate room to display the big image. You can pinch/zoom and also hit next/prev from there. Back will take you back to the prev room with the current page…

    This is actually a pet research project of mine. Shoot me an email if you have any other insight (seifert.md@gmail.com)

  6. Hmmmm…I’ll have to think on that. I really need to build a much larger database of images to see where it breaks. Let me know if you figure it out!

  7. Any idea on the limitations, as in, how many images are too many? The answer to that will tell me if this is what I need. At first glance, it surely is, but I’m afraid to get super deep into it only to break it with too many images. It’s run great in testing so far, but if there is any math that could tell me when to stop, I’d love to know beforehand. Thanks for the assett and any possible help!

  8. That’s a good question. I don’t have any specific answer as there are quite a few variables to consider, such as the intended platform, size of images, etc. This current design loads everything at once, so there is no streaming management that would allow you to only load what is “near to” visible. Size wise it would also matter how much memory the device has as that would also limit how many images can be loaded at any one time. If you just have small thumbnails, you could easily have several hundred on a PC without an issue ( I did something like that with this code ). Sorry I can’t be more helpful.

  9. nice tuts! i’ve been working on creating a gallery . but i try to make the image to have a gap/ space between them and nothing works so far. any idea ?

  10. Hey Slay! To put space in between each image is easy. In the scr_SpawnImage file, when you are declaring the offestX, you just need to add some space to the width of the sprite before multiplying. For example, this will give you an extra 64 pixels of space:
    offsetX = (sprite_get_width(theSprite) + 64) * i + 8;

    Hope that helps!

  11. Hi there, first I have to tank your for the tutorial. I getting a error when I try to integrate this on my project

    ———————————-
    FATAL ERROR in
    action number 1
    of Create Event
    for object obj_Page:

    Variable obj_Page.RIGHT(100011, -2147483648) not set before reading it.
    at gml_Script_scr_Page_Create (line 3) – myDir = RIGHT;
    ———————————-
    If I put RIGHT using (‘) and set all variables LEFT and RIGHT with (‘) and the error is gone but my image carousel just goes left and I can go back to right.
    I checked all the scripts and objects, I just change the sprites in the galleryList file but nothing else, so what might be the error?

  12. I’m not sure what is happening, but my guess is that you haven’t set RIGHT (and LEFT) as Macros in the Resources>Define Macros. In the tutorial I mention setting “constants” which is what GameMaker Studio used to call Macros. I also don’t explicitly state how to do this which may have caused some confusion. I plan on updating some of these to GameMaker Studio 2 and will keep this in mind!

    Hope that helps

  13. this example looks great, just what i need!

    however,
    .gmz file loaded into gm:s 1.4
    i get errors, and project does not work… 🙁

    can you please fix the file?

  14. I am in the process of updating all the tutorials to work both in GameMaker Studio 1 and 2. Check back soon 🙂

Leave a Reply