Tetris, creating pieces and detecting collisions
by James Ford · in Torque Game Builder · 07/06/2006 (12:40 pm) · 12 replies
I have already finished most of the functionality of a little tetris game. Unlike the tutorial on TDN I use staticsprites for pieces and setlinearvelocityy for movement (they move fluidly not by jumping down one row at a time).
A piece is constructed of blocks (a block staticsprite). The piece itself is only a scriptobject with a matrix[3x3] which contain the piece's blocks.
So to stop a piece, moving blocks send collisions and stationary blocks receive collisions and only moving blocks have collisioncallback on. Then,
Block::Oncollision(srcobj,dstobj, etc)
{
for(%i=0;%i<4;%i++)
{
for(%ii=0;%ii<4;%ii++)
{
$currentpiece.matrix[%i,%ii].setlinearvelocityY(0);
}
}
}
So if any part of the currentpiece (any block) collides with something (a stationary block), then all blocks in the currentpiece matrix will stop.
But HERES THE PROBLEM:
Sometimes when the current piece stops/collides, a few blocks get out of line, above or below where the "colliding" block stopped, this gap increases if they are traveling at higher speed. I dont know why its doing this, but its as if while the for loop is executing, the blocks that havent been stopped yet are still moving. Is that even possible? I would think that the scene would basically be paused while a function like oncollision is still resolving. I tried pausing the scene at the beginning of the oncollision function and unpausing it at the end, but this did not change the results.
I'm using for loops similar to this for all piece manipulation. Hopefully this isnt just conceptually the wrong way to do it, I hope it can work. An alternative could be having a "key" block in the center and mounting the other blocks onto it, which ill try if you guys think it would be a better idea.
Any tips pls?
A piece is constructed of blocks (a block staticsprite). The piece itself is only a scriptobject with a matrix[3x3] which contain the piece's blocks.
So to stop a piece, moving blocks send collisions and stationary blocks receive collisions and only moving blocks have collisioncallback on. Then,
Block::Oncollision(srcobj,dstobj, etc)
{
for(%i=0;%i<4;%i++)
{
for(%ii=0;%ii<4;%ii++)
{
$currentpiece.matrix[%i,%ii].setlinearvelocityY(0);
}
}
}
So if any part of the currentpiece (any block) collides with something (a stationary block), then all blocks in the currentpiece matrix will stop.
But HERES THE PROBLEM:
Sometimes when the current piece stops/collides, a few blocks get out of line, above or below where the "colliding" block stopped, this gap increases if they are traveling at higher speed. I dont know why its doing this, but its as if while the for loop is executing, the blocks that havent been stopped yet are still moving. Is that even possible? I would think that the scene would basically be paused while a function like oncollision is still resolving. I tried pausing the scene at the beginning of the oncollision function and unpausing it at the end, but this did not change the results.
I'm using for loops similar to this for all piece manipulation. Hopefully this isnt just conceptually the wrong way to do it, I hope it can work. An alternative could be having a "key" block in the center and mounting the other blocks onto it, which ill try if you guys think it would be a better idea.
Any tips pls?
About the author
http://jamesdev.info
#2
07/06/2006 (3:41 pm)
Well, mounting would keep things together fine, but I think it might get tricky for cases when pieces get broken up by lines being deleted, so it's probably best to work out an explicit movement method for keeping things together.
#3
07/06/2006 (3:58 pm)
One thing you could try is to et up the tetris pieces as one image each rather than using individual boxes. Set the bounding box to be a rectangle that encompases the whole piece, then do 'per pixel' collision tests when OnCollision is called.
#4
At the first collision, pause the whole game ! While paused run the setLinearVelocity routine and after it's completed resume the game.
07/06/2006 (6:55 pm)
My two cents :At the first collision, pause the whole game ! While paused run the setLinearVelocity routine and after it's completed resume the game.
#5
07/06/2006 (7:39 pm)
Just to pipe in for the sake of anyone coming back to this thread who is new with the engine. Bounding boxes HAVE to be convex. They can't be concave. It's probably just a brain-fart in James' second post, but I'm just trying to prevent confusion for others.
#6
Right now what I'm trying is, when a piece spawns I set each block within it to "moveto()" down one block-size-unit. And onpositiontarget I check for any stationary blocks or the edge of the gameborder, if so I stop the piece if not I "moveto" again. The complicated part is when they move the piece left or right or rotate. I store the moveto position in each block (currentpiece.matrix[%i,%ii].movetoposition), because I dont see any function like getpositiontarget(), then after I move the blocks left or right I move the positiontarget an equal distance then do moveto(positiontarget) again. For some reason while moving down one block unit, if you try to move left/right more than once, the moveto position gets messed up. Also I still dont know how to handle rotations.
07/09/2006 (10:29 am)
Yeah i had the two confused, oops! What do you mean by pause the whole game, I said I tried setscenepaused, what are you suggesting?Right now what I'm trying is, when a piece spawns I set each block within it to "moveto()" down one block-size-unit. And onpositiontarget I check for any stationary blocks or the edge of the gameborder, if so I stop the piece if not I "moveto" again. The complicated part is when they move the piece left or right or rotate. I store the moveto position in each block (currentpiece.matrix[%i,%ii].movetoposition), because I dont see any function like getpositiontarget(), then after I move the blocks left or right I move the positiontarget an equal distance then do moveto(positiontarget) again. For some reason while moving down one block unit, if you try to move left/right more than once, the moveto position gets messed up. Also I still dont know how to handle rotations.
#7
Each piece is an invisible "key" block and all visible blocks are mounted to it (in their apropriate places). This simplifies movement, I only have to move the mounted-to block. (I'm using moveto down one blockunit size and reissuing a moveto in onpositiontarget--or not if theres something already there). It would have simlified rotation as well, but to get all the pieces rotating properly I just had to do it manually. Eg: I created a matrix showing the piece position for each piece for each rotation. Then when the player rotates a piece I just delete the piece and create the piece with the new rotation in the same piece (reading the matrix and interpreting it took some thought). Removing lines was interesting. When a piece reaches a resting point it is deleted and replaced with individual (not mounted) blocks. I check each line starting at the bottom using pickrect and count the number of words in the obj list returned. If it is a complete line I delete it, then pick all blocks (pickrect) on the screen above that line and move them all down one block unit with setposition. Then keep going looking for complete lines.
Tadda!
07/17/2006 (10:41 am)
Well my game is basically working, heres what I did for those curious.Each piece is an invisible "key" block and all visible blocks are mounted to it (in their apropriate places). This simplifies movement, I only have to move the mounted-to block. (I'm using moveto down one blockunit size and reissuing a moveto in onpositiontarget--or not if theres something already there). It would have simlified rotation as well, but to get all the pieces rotating properly I just had to do it manually. Eg: I created a matrix showing the piece position for each piece for each rotation. Then when the player rotates a piece I just delete the piece and create the piece with the new rotation in the same piece (reading the matrix and interpreting it took some thought). Removing lines was interesting. When a piece reaches a resting point it is deleted and replaced with individual (not mounted) blocks. I check each line starting at the bottom using pickrect and count the number of words in the obj list returned. If it is a complete line I delete it, then pick all blocks (pickrect) on the screen above that line and move them all down one block unit with setposition. Then keep going looking for complete lines.
Tadda!
#8
I created a puzzle game awhile back that was kind of a Tetris/Zuma clone (weird sounding I know, but it's kind of an interesting idea). Anyway, what I did was create a tilelayer with collision enabled that acted as a container for the blocks (similiar to the example in the Teris tutorial). Then I applied a downward constant force to each new block sprite I created. This simulates gravity and starts each new block falling as soon as it's enabled.
What makes this approach so easy to implement is that you don't have to worry about setting and/or clearing the linear velocity for each block and you don't have to keep track of when and where block collisions are occuring. As long as collisions are enabled for the blocks, each block automatically comes to rest when it lands on top of another block or the bottom of the container. This way you can actually rotate the block in real-time without having to delete it and re-place it. I also used pickrect to remove groups of blocks and it worked great.
Anyway, just another approach that might be easier in certain contexts.
07/18/2006 (12:08 pm)
It sounds like you've solved your problem and got it working correctly but just to chime in on the whole approach and another possible implementation method for anyone coming accross this thread in the future:I created a puzzle game awhile back that was kind of a Tetris/Zuma clone (weird sounding I know, but it's kind of an interesting idea). Anyway, what I did was create a tilelayer with collision enabled that acted as a container for the blocks (similiar to the example in the Teris tutorial). Then I applied a downward constant force to each new block sprite I created. This simulates gravity and starts each new block falling as soon as it's enabled.
What makes this approach so easy to implement is that you don't have to worry about setting and/or clearing the linear velocity for each block and you don't have to keep track of when and where block collisions are occuring. As long as collisions are enabled for the blocks, each block automatically comes to rest when it lands on top of another block or the bottom of the container. This way you can actually rotate the block in real-time without having to delete it and re-place it. I also used pickrect to remove groups of blocks and it worked great.
Anyway, just another approach that might be easier in certain contexts.
#9
I thought about doing that but had two problems,
1. how do you definte a collision box for a piece that is concave
2. the blocks that make up my tetris pieces have a shadow and are of 7 different colors. So I would need a different sprite for every piece for every rotation for every color... just creating them with blocks, in game, wasn't that hard.
Actually the way i am doing it, by mounting all the blocks to one invisible object, I can rotate the pieces with setrotation, but different pieces have different centers of rotation and it is not actually the centerpoint of the piece for anyof them.
07/20/2006 (11:58 am)
So each of your pieces/shapes is its own staticsprite ?I thought about doing that but had two problems,
1. how do you definte a collision box for a piece that is concave
2. the blocks that make up my tetris pieces have a shadow and are of 7 different colors. So I would need a different sprite for every piece for every rotation for every color... just creating them with blocks, in game, wasn't that hard.
Actually the way i am doing it, by mounting all the blocks to one invisible object, I can rotate the pieces with setrotation, but different pieces have different centers of rotation and it is not actually the centerpoint of the piece for anyof them.
#10
Now I wasn't using a full Tetris set of blocks since my game was sort of a hybrid. I only used two block shapes but I don't see why this approach wouldn't allow you to simulate a concave collision box since collision is being detected on a block by block basis and not based on the invisible object that they're mounted to.
This also allows you to just rotate the invisible object without having to have different sprites for each position as you were able to do in your game as well. Although my shapes did have a uniform center point I don't see why you wouldn't be able mount your blocks in such a way as to have an accurate center point. But I'm sure you could calculate the rotatation/position offset around a different center point using a little Trigonometry.
It sounds like our games are somewhat different and therefore require a slightly different approach, but I can't see my implementation not accomplishing most of the things you needed to do. I'll experiment with a couple of different piece shapes and find out if it does work correctly.
07/20/2006 (1:06 pm)
I guess I should clarify that point. In my case, the pieces themselves aren't complete static sprites, though they do effectively function as if they were. I had a small square block with full collision that I used as a template and I just changed the image for the different pieces. I then mounted each block onto an invisible object that had a constant force applied it but it didn't have any collision detection.Now I wasn't using a full Tetris set of blocks since my game was sort of a hybrid. I only used two block shapes but I don't see why this approach wouldn't allow you to simulate a concave collision box since collision is being detected on a block by block basis and not based on the invisible object that they're mounted to.
This also allows you to just rotate the invisible object without having to have different sprites for each position as you were able to do in your game as well. Although my shapes did have a uniform center point I don't see why you wouldn't be able mount your blocks in such a way as to have an accurate center point. But I'm sure you could calculate the rotatation/position offset around a different center point using a little Trigonometry.
It sounds like our games are somewhat different and therefore require a slightly different approach, but I can't see my implementation not accomplishing most of the things you needed to do. I'll experiment with a couple of different piece shapes and find out if it does work correctly.
#11
I have a 4x4 matrix that stores the current piece's block positions, with 2,2 the position of the invisible block they are mounted to. So before I move the piece down one blockunit using moveto, I loop through the matrix and find the position of each block in the matrix (calculated from the mountedto blocksposition). Then I pickrect below each block checking for blocks in a stationaryblock graphgroup (blocks that have stopped). If I find something below then the piece stops (and is replaced with stationary blocks), if I dont then I moveto down one block unit. And this is reexecuted onpositiontarget.
I do something similar for rotations. When a key is pressed I subsitute the currentpiece's matrix with a matrix for that piece rotated one turn (I have a matrix for each piece for each rotation--because each piece does not rotate the same way). I have not actually rotated the piece yet I am just manipulating a matrix. Then I check for stationary blocks in the position of where each block in the matrix "would be" using pickrect and calculating that positon from the mountedto block's positon and each matrix-block's position relative to it.
07/21/2006 (10:01 am)
Then how are you doing collision detection? And how are you checking if a rotation is valid before executing?I have a 4x4 matrix that stores the current piece's block positions, with 2,2 the position of the invisible block they are mounted to. So before I move the piece down one blockunit using moveto, I loop through the matrix and find the position of each block in the matrix (calculated from the mountedto blocksposition). Then I pickrect below each block checking for blocks in a stationaryblock graphgroup (blocks that have stopped). If I find something below then the piece stops (and is replaced with stationary blocks), if I dont then I moveto down one block unit. And this is reexecuted onpositiontarget.
I do something similar for rotations. When a key is pressed I subsitute the currentpiece's matrix with a matrix for that piece rotated one turn (I have a matrix for each piece for each rotation--because each piece does not rotate the same way). I have not actually rotated the piece yet I am just manipulating a matrix. Then I check for stationary blocks in the position of where each block in the matrix "would be" using pickrect and calculating that positon from the mountedto block's positon and each matrix-block's position relative to it.
//$currentpiecematrix [4x4] =
//0 0 0 0
//1 1 1 1
//0 0 0 0
$blocksize = 24; //pixels, width, height
$z = $blocksize/2;
function testforRotation()
{
%piecex = $currentpiece.getpositionx();
%piecey = $currentpiece.getpositony();
for(%y=1;%y<5;%y++)
{
for(%x=1;%x<5;%x++)
{
if($currentpiecematrix[%x,%y])
{
%blockx = %piecex + (%x-2)*$blocksize;
%blocky = %piecey + (%y-2)*$blocksize;
%obj = scenegraph2d.pickrect(%blockx-%z,%blocky-%z,%blockx+%z,%blocky+%z,BIT($stationaryblocksGroup));
if(isobject(%obj))
return false;
}
}
}
return true;
}
#12
The code above works fine, I'm curious if anyone else knows perhaps an easier way to check if a rotation can be done (would the rotated piece be overlapping any existing blocks).
04/25/2007 (6:56 pm)
Just rereading my old posts... The code above works fine, I'm curious if anyone else knows perhaps an easier way to check if a rotation can be done (would the rotated piece be overlapping any existing blocks).
Associate James Ford
Sickhead Games
Maybe if I used moveto() exclusively and never used setlinearvelocity or collisions, and projected the pieces stopping point with pickrect looking for obstacles. I can now imagine how that could work, but it would be a lot more work than just using setlinearvelocity and oncollision.
And I know nothing about mounting so who knows if that would be easier or not.