Drag and Drop Tutorial-ish thing
Posted: Wed Nov 02, 2011 1:59 am
So I've been asked to do a tutorial on the drag and drop system. I'm not sure how well I can explain its uses, but apparently that's not a problem...
disclaimer: I'm an engineering dropout. My way of coding is not necessarily proper.
Of course, trying to explain things aimlessly would be silly, so I'm going to use an example mini-game and just explain how it works and how I thought it up. Now then,
How to make a jigsaw puzzle using drag and drop!!
Specifically, the following is how to make a puzzle that gives the player a disorganized mess of 12 pieces next to a 3 x 4 grid, then expects them to form a picture. The fully working code is attached, and uses an empty space image made in 10 sec on paint, as well as a random drawing I had lying around.
Also, out of personal preference, the example will use as little in python as possible, so that it integrates better with renpy. With only a little work, this example could have a cutscene that interrupts the puzzle to show an unrelated conversation, before going right back to the unchanged puzzle. Also, I have trouble mentally dealing with vbox, hbox, and grid.
First, let's look at what the documentation shows us how to do. The first example doesn't really help, as a puzzle mini-game of any sort doesn't really need draggable windows are anything like that. The second one, however, shows how to keep track of what the player has done with your draggable stuff. This is the important part.
So how does that example work? It hands the draggable objects (the detectives) a function (the part with def) to use to what to do when they're dragged around. That function then uses this code:to check if the draggable object was placed on one of the specified drops (the places for the detectives to go). If it wasn't then nothing happens. If it was, the function continues on to the next part and records the thing that was dragged, and where it was dragged to, using this part:
here, "store." is used to connect to the actual renpy code. drags[0] is used to specify that only the one object was dragged (it's possible to move multiple, if they're set up to do so). "return True" tells the program that the player did something meaningful.
So how does this relate to the jigsaw puzzle? It's almost the same code I used. In fact, you could use this for most mini-games made with drag and drop, just with slight variation.
Back to the main point, there's one very big difference between the detectives example and a jigsaw puzzle. That is, a jigsaw puzzle requires the player be able to place pieces over and over again, possibly in the wrong spots. This means two things:
1. The program needs to keep track of the positions of the pieces and update regularly, and
2. The program needs to be able to tell when the pieces are in the correct positions
Luckily, both of those requirements can be solve in the same way. The program needs a list of positions corresponding to each piece. That way, each time the player places a piece, the game can check the piece positions to see if they're correct. In my code, I used piecelist which contains a list of two element list (x and y), so my code comes out like this:
You might notice the strange name I chose. This is so that the first two characters can be parsed into an integer. You'll see why later.
Now then. The draggable piece are defined, but the puzzle still needs spaces to put the pieces. Rather than keeping track of current positions, the places merely need a set of coordinate. It would be fine to enter the coordinate in, but it will actually save typing to use lists for this as well. Thus my code for the spaces looks like this:
Here again, you see my strange naming scheme. This time however, the reason is so that the coordinates within the grid of empy spaces are easy to use. Thus, the two characters are actually two separate cooridinates (x and y).
Now then, after copying and pasting a whole bunch, there are now 12 of each type. The next thing to address, is the needed python function. In my code above, I used piece_dragged. As I stated earlier, the python function can be almost the same as that of the documentation. The only difference is that the function used for the jigsaw puzzle needs to keep track of the pieces even if the player doesn't place them in the slots. This is easier to show than to explain so here it is:
Ugh! this looks so complicated and messy. What's going on here is a bunch of parsing (changing the format of the values to fit a different type). In this case, the information provided is in strings, but the values needed are integers. This is what I would guess as the hardest for renpy users who don't code much, as without sufficient knowlege of python it looks like gibberish. Let's break it down.
these lines store the new coordinates for a piece that wasn't moved onto one of the slots. the function int() is used to change the characters in drag_name into digits. It takes the first digit in drag_name, multiplies it by ten, then adds it to the second digit, giving the number for the piece that was moved. This number then accesses the coordinate in piecelist that corresponds to the moved piece so that the new coordinates can be stored.
The other part,
does almost the same thing, except that it just does the parsing so that actual work can be done elsewhere.
Still there? If that explanation just now didn't makes sense, then just trust me that it works, and ask for help if you need to do something similar.
So the code now has most of it's functionality, but it still need to work with the pieces that are in the slots. Before getting into the code for this, another possibility needs to be considered: What should the game do if the player places two pieces in the same spot? For various reasons, I chose to swap the pieces. Thus, my code covers two situations:
1. when a piece in a slot is placed in another slot with a piece in it, and
2. when a piece that's outside of the slots is placed in a slot with a piece in it
Both of these require a bit of juggling with the value. For this reason, I made two arbitrarily named variables to hold the values momentarily. Here's what that looks like:
the first line checks if a piece is already in the slot, by checking if any of the coordinates in piecelist match the slot in question. If so, the pieces are swapped using more ugly python code. t1 contains the previous coordinates for the piece that as dragged. t2 contains the number for the piece that's in the wanted slot.
Hooray! the puzzle is almost playable. This is meaningless, though, if it doesn't check the win condition. As stated earlier, this can be done by comparing the current piece positions to the correct ones. Once again the coordinate lists can be used to save on typing (because they can be copy-pasted).
There are only two things left to do:
1. put the whole thing in a loop
2. set up the variables so that renpy knows what they are
for 1., the following format is probably easiest:
for 2., all that is needed is to supply the starting values of everything, in the format that will be used like so
The reason piecelist has so many values, is because renpy will try to evaluate the screen early, and thus will get an out of bounds error otherwise (<--this was hell for me to figure out). The reason for the random integers and the for loop is so that the piece don't appear in any particular order.
Finally! it is finished! I hope this is helpful. Attached is the complete working code.
On a last note, if anyone's interested, I also have mostly working code for a strategic board game, that uses much more advanced code. It's a triangular version of Ataxx (aka. infection)
disclaimer: I'm an engineering dropout. My way of coding is not necessarily proper.
Of course, trying to explain things aimlessly would be silly, so I'm going to use an example mini-game and just explain how it works and how I thought it up. Now then,
How to make a jigsaw puzzle using drag and drop!!
Specifically, the following is how to make a puzzle that gives the player a disorganized mess of 12 pieces next to a 3 x 4 grid, then expects them to form a picture. The fully working code is attached, and uses an empty space image made in 10 sec on paint, as well as a random drawing I had lying around.
Also, out of personal preference, the example will use as little in python as possible, so that it integrates better with renpy. With only a little work, this example could have a cutscene that interrupts the puzzle to show an unrelated conversation, before going right back to the unchanged puzzle. Also, I have trouble mentally dealing with vbox, hbox, and grid.
First, let's look at what the documentation shows us how to do. The first example doesn't really help, as a puzzle mini-game of any sort doesn't really need draggable windows are anything like that. The second one, however, shows how to keep track of what the player has done with your draggable stuff. This is the important part.
So how does that example work? It hands the draggable objects (the detectives) a function (the part with def) to use to what to do when they're dragged around. That function then uses this code:
Code: Select all
if not drop:
return
Code: Select all
store.detective = drags[0].drag_name
store.city = drop.drag_name
return True
So how does this relate to the jigsaw puzzle? It's almost the same code I used. In fact, you could use this for most mini-games made with drag and drop, just with slight variation.
Back to the main point, there's one very big difference between the detectives example and a jigsaw puzzle. That is, a jigsaw puzzle requires the player be able to place pieces over and over again, possibly in the wrong spots. This means two things:
1. The program needs to keep track of the positions of the pieces and update regularly, and
2. The program needs to be able to tell when the pieces are in the correct positions
Luckily, both of those requirements can be solve in the same way. The program needs a list of positions corresponding to each piece. That way, each time the player places a piece, the game can check the piece positions to see if they're correct. In my code, I used piecelist which contains a list of two element list (x and y), so my code comes out like this:
Code: Select all
drag_name "01 piece"
child im.Crop("ShizukaClassroom_0001.jpg", 120,0, 120, 207)
#^this is an old function for cropping, since the crop warper
#didn't work
droppable False
dragged piece_dragged
xpos piecelist[1][0] ypos piecelist[1][1]
#^the first number is which piece, the second is which coordinate
Now then. The draggable piece are defined, but the puzzle still needs spaces to put the pieces. Rather than keeping track of current positions, the places merely need a set of coordinate. It would be fine to enter the coordinate in, but it will actually save typing to use lists for this as well. Thus my code for the spaces looks like this:
Code: Select all
drag:
drag_name "00"
child "empty space.png"
draggable False
xpos coorlistx[0] ypos coorlisty[0]
Now then, after copying and pasting a whole bunch, there are now 12 of each type. The next thing to address, is the needed python function. In my code above, I used piece_dragged. As I stated earlier, the python function can be almost the same as that of the documentation. The only difference is that the function used for the jigsaw puzzle needs to keep track of the pieces even if the player doesn't place them in the slots. This is easier to show than to explain so here it is:
Code: Select all
def piece_dragged(drags, drop):
if not drop:
store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][0] = drags[0].x
store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][1] = drags[0].y
return
store.movedpiece = int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1])
store.movedplace = [int(drop.drag_name[0]), int(drop.drag_name[1])]
return True
Code: Select all
store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][0] = drags[0].x
store.piecelist[(int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1]))][1] = drags[0].y
The other part,
Code: Select all
store.movedpiece = int(drags[0].drag_name[0]) * 10 + int(drags[0].drag_name[1])
store.movedplace = [int(drop.drag_name[0]), int(drop.drag_name[1])]
Still there? If that explanation just now didn't makes sense, then just trust me that it works, and ask for help if you need to do something similar.
So the code now has most of it's functionality, but it still need to work with the pieces that are in the slots. Before getting into the code for this, another possibility needs to be considered: What should the game do if the player places two pieces in the same spot? For various reasons, I chose to swap the pieces. Thus, my code covers two situations:
1. when a piece in a slot is placed in another slot with a piece in it, and
2. when a piece that's outside of the slots is placed in a slot with a piece in it
Both of these require a bit of juggling with the value. For this reason, I made two arbitrarily named variables to hold the values momentarily. Here's what that looks like:
Code: Select all
if ([coorlistx[movedplace[0]], coorlisty[movedplace[1]]] in piecelist):
python:
t1 = piecelist[movedpiece]
t2 = piecelist.index([coorlistx[movedplace[0]], coorlisty[movedplace[1]]])
piecelist[movedpiece] = [coorlistx[movedplace[0]],coorlisty[movedplace[1]]]
piecelist[t2] = t1
else:
$ piecelist[movedpiece] = [coorlistx[movedplace[0]],coorlisty[movedplace[1]]]
Hooray! the puzzle is almost playable. This is meaningless, though, if it doesn't check the win condition. As stated earlier, this can be done by comparing the current piece positions to the correct ones. Once again the coordinate lists can be used to save on typing (because they can be copy-pasted).
There are only two things left to do:
1. put the whole thing in a loop
2. set up the variables so that renpy knows what they are
for 1., the following format is probably easiest:
Code: Select all
label puzzle:
call screen jigsaw
# update stuff here
# check win condition
jump puzzle
Code: Select all
label start:
scene black
image whole = "ShizukaClassroom_0001.jpg"
python:
coorlistx = [10, 130, 250, 370]
coorlisty = [10, 217, 424]
piecelist = [[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]]
for i in range(12):
x = renpy.random.randint(0, 59) + 621
y = renpy.random.randint(0, 480)
piecelist[i] = [x,y]
movedpiece = 0
movedplace = [0, 0]
jump puzzle
Finally! it is finished! I hope this is helpful. Attached is the complete working code.
On a last note, if anyone's interested, I also have mostly working code for a strategic board game, that uses much more advanced code. It's a triangular version of Ataxx (aka. infection)