GameMaker Tutorials: Dynamic Puzzle Pieces

 

In this tutorial we are going to look at building a system to slice up an image into pieces, shuffle them around, swap the pieces and finally, solve the puzzle. While this may sound quite complex, the system doesn’t need very much code and is very expandable. The general premise to create each piece dynamically is to use a mask shape to determine the size of the piece and then draw a specific section of the total image. As for shuffling and solving the puzzle, we will utilize a couple of data structures to do all the hard work for us. Let’s dive right in!

Download the Work File (.gmz)

Building the Puzzle Piece

The very first thing we need to do is to build the puzzle piece functionality, as all of our other code will be affecting it. Since we want to create these pieces dynamically, we will want to keep the size and shape separate from the image we are chopping up. The supplied work file comes with three sprites, a small rectangle and a large rectangle that will be used as masks to create the piece and an image for the puzzle. When constructing the masking sprites, it is important to make sure they are a divisor of the size of the puzzle image (as in cuts up appropriately).

We only need one piece, so make a new object and call it obj_PuzzlePiece. Each piece will need to draw its own section of the total puzzle which is easily done using the draw_sprite_part function. Before we get to that however, we need to initialize some variables. We create variables for: the sprite to display, the X and Y coordinates, and its width and height. We are also setting a boolean variable for whether the piece is selected or not. All the values are set to zero as we will be changing them on the fly later.

mySprite = 0;
myX = 0;
myY = 0;
myWidth = 0;
myHeight = 0;

isSelected = false;

Next we will draw the sprite. Using draw_sprite_part allows us to draw only a specified area of an image rather than drawing the entire image. The parameters for this function set the image we want to show, what frame of animation, set the coordinates and area of the image to draw, and then finally, where to place it on screen. This way we can have a unique portion of the image for each instance of obj_PuzzlePiece. As you can see in the code below, we need some user feedback for whether the piece has been selected. To keep things simple, we will set the alpha in half if selected. Remember to set the alpha back to full at the end, otherwise everything will be drawn partially transparent!

if (isSelected)
{
    draw_set_alpha(0.5);
}
draw_sprite_part(mySprite,0, myX, myY, myWidth, myHeight, x, y);

draw_set_alpha(1.0);

Creating the Puzzle

Now that we have the piece built, we can move onto creating the puzzle itself. We want to build a system that will take an image, chop it up into small pieces, then scramble it all up and place it on screen. We will also want to store the correct placement order for later so we can solve the puzzle. For this we will create a script, scr_PuzzleCreator that accepts some arguments and does everything we require.

We are going to need a whole lot of variables, the majority of which are disposable. We will pass four arguments to this script: the image for the puzzle, the mask to cut out, and an offset in case we don’t want the puzzle starting in the upper left corner of the room. We then create variables for the width and height of both the image and the mask.  We can get the values directly from the source using sprite_get_width and sprite_get_height. Finally, we set variables for how many rows and columns the puzzle will have, which we get by dividing the image size by the mask size.

var thePieceSprite = argument0; 
var thePieceMask = argument1;
var puzzleOffsetX = argument2;
var puzzleOffsetY = argument3;

var spriteWidth = sprite_get_width(thePieceSprite);
var spriteHeight = sprite_get_height(thePieceSprite);
var maskWidth = sprite_get_width(thePieceMask);
var maskHeight = sprite_get_height(thePieceMask);

var puzzleRows = spriteWidth / maskWidth;
var puzzleCols = spriteHeight / maskHeight;

If that seems like a lot of variables, just wait, we have some more! We want to store all these pieces into a data structure that we can then use later and we need something to help us shuffle the puzzle. We will use two lists and a grid for this. One list will hold the pieces in their original order, while another will be used to shuffle it all up. The grid will be used to keep track of the X and Y coordinates of each piece.

puzzleArray = ds_list_create();
puzzleShuffle = ds_list_create();
puzzleGrid = ds_grid_create(2,(puzzleRows * puzzleCols));

Now we are getting to the fun part, manufacturing all of the pieces! We will need two for loops to go through each row and column in the puzzle. For each section of this puzzle we create an instance of obj_PuzzlePiece and place it appropriately. We then apply the sprite and mask to the instance, plus set the variables for the X and Y coordinates and size that will be drawn on screen. Finally, we add the id of each piece to both the lists and place their X and Y position into the grid.

for ( var row = 0; row < puzzleRows; row++ )
{
    for ( var col = 0; col < puzzleCols; col++ )
    {
        var thePiece = instance_create(maskWidth * row, maskHeight * col, obj_PuzzlePiece);
        thePiece.mySprite = thePieceSprite;
        thePiece.mask_index = thePieceMask;
        thePiece.myX = maskWidth * row;
        thePiece.myY = maskHeight * col;
        thePiece.myWidth = maskWidth;
        thePiece.myHeight = maskHeight;
        ds_list_add(puzzleArray, thePiece);
        ds_list_add(puzzleShuffle, thePiece);
        ds_grid_set(puzzleGrid, 0, ds_list_size(puzzleArray) - 1, thePiece.myX + puzzleOffsetX);
        ds_grid_set(puzzleGrid, 1, ds_list_size(puzzleArray) - 1, thePiece.myY + puzzleOffsetY);
    }
}

At this point we have created all the pieces and stored their info, all that is left to do is to shuffle the pieces around. We create a variable for the total amount of pieces of the puzzle and then shuffle one of the lists into a random order. We then use one more loop, going through the now shuffled list from top to bottom and move each piece a new location on the puzzle grid. (To better understand what our data structure looks like, see the example image below) Finally, at the very end we destroy the shuffled list as we no longer need it and we don’t want a memory leak.

 

totalPieces = ds_list_size(puzzleArray);
ds_list_shuffle(puzzleShuffle);
for (i=0; i < totalPieces; i++)
{
    thePiece = ds_list_find_value(puzzleShuffle, i)
    thePiece.x = ds_grid_get(puzzleGrid,0,i);
    thePiece.y = ds_grid_get(puzzleGrid,1,i);
}
ds_list_destroy(puzzleShuffle);

tableBreakdown

Speaking of destroying our data structures, we should make sure we don’t forget about our grid and other list. We should create a clean up script that we can call at some point if we no longer need these.

ds_list_destroy(puzzleArray);
ds_grid_destroy(puzzleGrid);

We need to test our puzzle creator so let’s create an obj_Overlord object and use it to create our puzzle. Since we have two masks from which to choose from, why don’t we also randomize it.

randomMask = choose(spr_PieceMask_Large, spr_PieceMask_Small);
scr_PuzzleCreator(spr_Puzzle_Image, randomMask, 0, 0);

Test it out and you should see that the image is chopped up into several small pieces and in an assorted order. Now it’s time to make it interactive!

Solving the Puzzle

Before we can actually solve the puzzle, we need to be able to move the pieces around. For this we will keep it fairly simple and make it a straight swap between two clicked pieces. We will use some global variables to keep track of the first clicked piece, which we will initialize using the Overlord at the start of the game. We will also use the randomize function to ensure that the random functions are truly random.

global.pieceSelected = false;
global.piece = 0;
global.pieceX = 0;
global.pieceY = 0;
randomize();

When clicking on a piece we need to know whether it is the first or second piece selected. When it is the first piece selected, we check to see if it is has been previously selected as the piece. This is necessary to do as without this check the first piece will remain selected and will just hop from spot to spot. If it is a new first piece, we assign the global variables for the instance and coordinates. We also set the local variable for isSelected, which we use in the puzzle piece Draw event to indicate the selection, and then exit this script. If the piece happens to be the second selection, we move the first selected piece to the second position, and then move the second to the first position. We reset the selection variables to false and at the end we execute a User Defined event inside of the Overlord, before exiting.

if (!global.pieceSelected)
{
    if (global.piece != self.id)
    {
        global.piece = self.id;
        global.pieceX = x;
        global.pieceY = y;
        global.pieceSelected = true;
        isSelected = true;
        exit;
    }
} else {
    global.piece.x = x;
    global.piece.y = y;
    x = global.pieceX;
    y = global.pieceY;
    global.piece.isSelected = false;
    global.pieceSelected = false;
    with(obj_Overlord) {event_user(0);}
    exit;
}

We can now move pieces around, which is good, but we still need to solve the puzzle. For this we will want to create a script specifically for this task, scr_CheckForVictory. We will run this script after every move and it will return whether the puzzle has been solved or not. The solution is fairly easy to implement as we already stored all the necessary data. We just need to run a loop that looks at each individual piece, as found in the list, and see if the X and Y coordinates are the same as the ones found in the grid. If any piece is found in the wrong spot, then the puzzle has not been solved. Otherwise it has been solved, and we return that value.

isVictory = false;
for (i=0; i < totalPieces; i++)
{
    thePiece = ds_list_find_value(puzzleArray, i);
    if (thePiece.x != ds_grid_get(puzzleGrid,0,i) || thePiece.y != ds_grid_get(puzzleGrid,1,i))
    {
        return isVictory;
    }
}
isVictory = true;
return isVictory;

All that remains is to run this check and then do something if it has been solved. For this example all we will do upon solving the puzzle is to destroy the puzzle, clean up the data structures and then create a new puzzle in its place. We will run all of this in the Overlord Other | User Defined event so that it happens after every move.

if (scr_CheckForVictory())
{
    with(obj_PuzzlePiece){ instance_destroy();}
    scr_CleanUp();
    scr_Overlord_Create();
}

There we go! We now have a system where we can utilize any image, chop it up into any size pieces that we want, and display it in a random order. If a player rearrange the pieces into the correct order, this system will recognize that success has been achieved. We covered how to draw a part of a sprite and we used a couple of different data structures. Now it is your chance to go off and build upon the lessons learned here. Maybe add some more masks for different size pieces or change how the pieces are moved around the board. I look forward to seeing it in your games!

Download the Completed File (.gmz)

If you liked this tutorial and are thinking “If only there were a book with more of these tutorials.” Then you are in luck! 
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

20 thoughts on “GameMaker Tutorials: Dynamic Puzzle Pieces

  1. Hey Jason!
    Love your book and use it as reference all the time when I’m in a GameMaker bind. I especially like the Dynamic Front Ends and Playing with Particles chapters.
    I’m interested in using your Dynamic Puzzle Pieces tutorial and files and found the following fatal error occurs when running the Completed File in GM: scr_Screen_PuzzleCreator(line 36) – ds_list_copy(obj_ScreenOverlord.solveArray,puzzleShuffle).
    Without taking a lot of your time, would you provide any hints on how to fix the issue?
    Thanks so much and keep up the great work!
    Mag Webber

  2. There is a line 36 in scr_PuzzleCreator which causes the error. It says “ds_list_copy(obj_ScreenOverlord.solveArray, puzzleShuffle);”, just delete this line and everything works correctly.

    Thanks for this info, this is really helpful.
    Ray.

  3. Also, I’ve added a line randomize(); to ths script, otherwise the puzzle is always generated the same way.

  4. LOL, another addition.
    Better put the randomize() function in the scr_Overlord_Create script, this will also randomize the choose function which determines whether you will have small or large puzzle blocks.

  5. Yeah, randomize wasn’t always necessary, but now you have to use it. You should only have to use it once however as it just randomizes the random seed for everything 😉

  6. Hi its a great tutorial, thank you for sharing

    Need Help

    I am having problem at scr_PuzzleCreator continued…for loop,
    var thePiece = instance…………………………..;
    thePiece.mySprite = thePieceSprite — ERROR:- ‘error…. variable name expected’

  7. Unfortunately I can’t help you without more information. My initial guess would be a typo somewhere in your code, possibly thePieceSprite. Did you compare it to the completed GMZ file?

  8. FATAL ERROR in
    action number 1
    of Mouse Event for Left Pressed
    for object obj_PuzzlePiece:

    global variable pieceSelected(100000, -2147483648) not set before reading it.
    at gml_Script_scr_PuzzlePiece_Pressed (line 1) – if (!global.pieceSelected)
    ############################################################################################
    ——————————————————————————————–
    stack frame is
    gml_Script_scr_PuzzlePiece_Pressed (line 1)
    called from – gml_Object_obj_PuzzlePiece_LeftButtonPressed_1 (line 1) – scr_PuzzlePiece_Pressed(0,0,0,0,0);

    hey.. i got this error when i move the room puzzle into the game level. can you help me.

  9. I’m not quite sure, but from the looks of it,it seems to be that the global variable (global.pieceSelected) might not be initialized. Is your global object in the room (or in a preceding room)?

  10. Hi Jason!
    Men, thanks for this tutorial and document, you don’t imagine how much helpful this was to me!
    My question is: What can I change in the codes that makes me able to use any size of Puzzle Image?
    I’ve tryed some things but non works… When I use an image with a different size it creates black holes in the puzzle when playing.
    Probably it is a small thing that I’m not seeing, like allways rsrs.
    Thank you 😉

  11. hi Jason,
    This is great tutorial, i had learn so much. Thanks so much

    Now i’m changing something about it.

    for example i want the script work with a lot of pic, not just with one and i want everytime i resolve the pic, the solved pic is delete

    i had create a script with ds_list with a lot of pic, i had named scrpt_list()

    i put this script in scr_Overlord_Create

    now in scr_Overlod_Create i have this code:

    scrpt_list();

    randomMask = choose(spr_PieceMask_Large, spr_PieceMask_Small);
    scr_PuzzleCreator((ds_list_find_value(immagine,ds_list_size(immagine)-1)), randomMask, 0, 100);

    randomize();

    in scr_CheckForVictory i add:

    ds_list_delete(immagine,0)

    the problem is if i solve the pic that is not delete.

    i think because i have scrpt_list in scr_Overlord_create.
    everytime scr_Overloard_Create work create a new list with all pics.

    But if i put scrpt_list in the create event or in Game Start event (obj_Overlord) say me the ds_list is not setted.

    can you help me with this problem?

  12. Hi Jason. Thanks so much for this tutorial, it help me a lot!
    Without taking a lot of your time, can you tell me what to do to check if the player change the pieces correctly? Like give him a point if the piece selected was put in the right place.
    Thanks 🙂

  13. I solved the problem.

    It was simple:

    I delete scrpt_list in scr_Overlod_Create.

    and in obj_overlord:

    i put in create:
    scrpt_list();
    alarm[1]=30;

    and in alarm:
    scr_Overlod_Create();

  14. I’m not sure what you mean by black holes. Is there space between each image or is it just on some image, such as only along the bottom row images. One thing to look at is to see if the size of the Mask is divisible by the image size in whole numbers. If it doesn’t match, such as the image is 800 pixels wide and the mask is 128 pixels, that means it will add an extra piece to the end (7 pieces, but should be 6.25). Hope that helps.

  15. I think you are close, though I can see issues arising. Make sure you put the found value of your immagine list into a variable. From what I see it looks like you are grabbing the last image in the list, but then deleting the first one. Secondly, you don’t need to do that in the Victory script, but should do it just after the puzzle is created. You have already used the image and the next reload should pull from the same list which would now have less images. Hopefully that makes sense!

  16. The easiest way to do this would probably be to put a variable into each piece with the corresponding number of where it is in the puzzleArray, as in the first piece has a myNum variable set to 0. That way you could directly compare the clicked / placed piece to the Array and confirm it is in the correct location, as is done in the victory script (just not in a for loop as you know what position to search).

    That being said, giving points in that way probably isn’t a good idea as a player will just swap pieces over and over again, racking up the points. To fix that you might think about a freeze system that makes correctly placed blocks unusable afterwards.

Leave a Reply