Collision problems with drag-n-drop puzzle game.
- by Amplify91
I am working on an Android game similar to the Rush Hour/Traffic Jam/Blocked puzzle games. The board is a square containing rectangular pieces. Long pieces may only move horizontally, and tall pieces may only move vertically. The object is to free the red piece and move it out of the board. This game is only my second ever programming project in any language, so any tips or best practices would be appreciated along with your answer.
I have a class for the game pieces called Pieces that describes how they are sized and drawn to the screen, gives them drag-and-drop functionality, and detects and handles collisions.
I then have an activity class called GameView which creates my layout and creates Pieces objects to add to a RelativeLayout called Board. I have considered making Board its own class, but haven't needed to yet.
Here's what my work in progress looks like:
My Question:
Most of this works perfectly fine except for my collision handling. It seems to be detecting collisions well but instead of pushing the pieces outside of each other when there is a collision, it frantically snaps back and forth between (what seems to be) where the piece is being dragged to and where it should be. It looks something like this:
Another oddity: when the dragged piece collides with a piece to its left, the collision handling seems to work perfectly. Only piece above, below, and to the right cause problems.
Here's the collision code:
@Override
public boolean onTouchEvent(MotionEvent event){
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//check if touch is on piece
if (eventX > x && eventX < (x+width) && eventY > y && eventY < (y+height)){
initialX=x;
initialY=y;
break;
}else{
return false;
}
case MotionEvent.ACTION_MOVE:
//determine if piece should move horizontally or vertically
if(width>height){
for (Pieces piece : aPieces) {
//if object equals itself in array, skip to next object
if(piece==this){
continue;
}
//if next to another piece,
//do not allow to move any further towards said piece
if(eventX<x&&(x==piece.right+1)){
return false;
}else if(eventX>x&&(x==piece.x-width-1)){
return false;
}
//move normally if no collision
//if collision, do not allow to move through other piece
if(collides(this,piece)==false){
x = (eventX-(width/2));
}else if(collidesLeft(this,piece)){
x = piece.right+1;
break;
}else if(collidesRight(this,piece)){
x = piece.x-width-1;
break;
}
}
break;
}else if(height>width){
for (Pieces piece : aPieces) {
if(piece==this){
continue;
}else if(collides(this,piece)==false){
y = (eventY-(height/2));
}else if(collidesUp(this,piece)){
y = piece.bottom+1;
break;
}else if(collidesDown(this,piece)){
y = piece.y-height-1;
break;
}
}
}
invalidate();
break;
case MotionEvent.ACTION_UP:
// end move
if(this.moves()){
GameView.counter++;
}
initialX=x;
initialY=y;
break;
}
// parse puzzle
invalidate();
return true;
}
This takes place during onDraw:
width = sizedBitmap.getWidth();
height = sizedBitmap.getHeight();
right = x+width;
bottom = y+height;
My collision-test methods look like this with different math for each:
private boolean collidesDown(Pieces piece1, Pieces piece2){
float x1 = piece1.x;
float y1 = piece1.y;
float r1 = piece1.right;
float b1 = piece1.bottom;
float x2 = piece2.x;
float y2 = piece2.y;
float r2 = piece2.right;
float b2 = piece2.bottom;
if((y1<y2)&&(y1<b2)&&(b1>=y2)&&(b1<b2)&&((x1>=x2&&x1<=r2)||(r1>=x2&&x1<=r2))){
return true;
}else{
return false;
}
}
private boolean collides(Pieces piece1, Pieces piece2){
if(collidesLeft(piece1,piece2)){
return true;
}else if(collidesRight(piece1,piece2)){
return true;
}else if(collidesUp(piece1,piece2)){
return true;
}else if(collidesDown(piece1,piece2)){
return true;
}else{
return false;
}
}
As a second question, should my x,y,right,bottom,width,height variables be ints instead of floats like they are now?
Also, any suggestions on how to implement things better would be greatly appreciated, even if not relevant to the question! Thanks in advance for the help and for sitting through such a long question!
Update:
I have gotten it working almost perfectly with the following code (this doesn't include the code for vertical pieces):
@Override
public boolean onTouchEvent(MotionEvent event){
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//check if touch is on piece
if (eventX > x && eventX < (x+width) && eventY > y && eventY < (y+height)){
initialX=x;
initialY=y;
break;
}else{
return false;
}
case MotionEvent.ACTION_MOVE:
//determine if piece should move horizontally or vertically
if(width>height){
for (Pieces piece : aPieces) {
//if object equals itself in array, skip to next object
if(piece==this){
continue;
}
//check if there the possibility for a horizontal collision
if(this.isAllignedHorizontallyWith(piece)){
//check for and handle collisions while moving left
if(this.isRightOf(piece)){
if(eventX>piece.right+(width/2)){
x = (int)(eventX-(width/2)); //move normally
}else{
x = piece.right+1;
}
}
//check for and handle collisions while moving right
if(this.isLeftOf(piece)){
if(eventX<piece.x-(width/2)){
x = (int)(eventX-(width/2));
}else{
x = piece.x-width-1;
}
}
break;
}else{
x = (int)(eventX-(width/2));
}
The only problem with this code is that it only detects collisions between the moving piece and one other (with preference to one on the left). If there is a piece to collide with on the left and another on the right, it will only detect collisions with the one on the left. I think this is because once it finds a possible collision, it handles it without finishing looping through the array holding all the pieces. How do I get it to check for multiple possible collisions at the same time?