# Tiled based map collision detection problem

I'm working on a tile-based Mario clone.

This works all fine when walking and falling. But when the player jumps near a wall and walks to the right in the air, then the player get stuck on the wall. The player sprite falls again when the player releases the key.

The setup is pretty easy and i can't find the problem. The map is build up as a 2D array with map blocks. A block can be solid or not. Player can't move trough solid objects, duh..

In the game loop:

• the player's location gets updated (gravity, movement ect.)
• check the map on collisions. When a collision is found in the Y, then the player location gets updated so it's above or below the block (depending the direction of the player), then the collision box gets updated with the new location. Then the same process for the X.
• The collision box gets updated to the new location (a free location). The box is adjusted so that it's little higher to get the block below the player to check if he's landed. This is to change the state of the player, from flying sprite to idle ect.

I also tried to switch the X and Y check so that the player is moved on the X line. Then when the player moves the movement goes very laggy. When i push and release the button to move, then the player moves faster but in chucks. Very trippy..

Does anyone sees the error or can give me a better collision algorithm for this?

## UPDATE (didn't updated the code)

I swaped the x and y check method and implemented the isonland variable. So when walking and jumping against walls works perfect. Only now, when the player jumps Mario get set back when landed. This because the X check method goes first and adjust Mario's position.

How can I solve that?

Map class update method:

public void update(int timeElapsed) {
//update entities
for(Entity entity : _mapEntities) {
entity.update(timeElapsed);
}

//update objects
for(MapObject mapObt : _mapObjects) {
mapObt.update(timeElapsed);
}

//check for collisions
checkMapCollision();
}


Entity (abstract) update method:

public void update(int timeElapsed) {
_velocity = new Vector2d(0.0F, 0.0F);

//add gravity
_velocity.y = Map._GRAVITY_PER_SEC * timeElapsed;
}


Mario (extends entity) update methos:

@Override
public void update(int timeElapsed) {
super.update(timeElapsed);

if(_state == STATES.IDLE) {

} else if(_isMoving) {
_marioSmallWalk.update(timeElapsed);
}

if(_state == STATES.JUMPING) {
setVelocityY(getVelocity().y + _jumpSpeed);

_jumpSpeed += _JUMP_DECREASE * timeElapsed;

//falling?
if(getVelocity().y > 0) {
setState(STATES.FALLING);
}
}

if(_isMoving) {
double walkSpd = (_WALK_SPEED_SEC * timeElapsed);

if(getFacing() == FACING.LEFT) {
walkSpd = -walkSpd;
}

setVelocityX(getVelocity().x + walkSpd);
}

//falling?
if(getVelocity().y > (Map._GRAVITY_PER_SEC * timeElapsed) + 1.0F) {
setState(STATES.FALLING);
}

setPosition((int)(getX() + getVelocity().x), (int)(getY() + getVelocity().y));
}


Map class CheckMapCollision method:

public void checkMapCollision() {
//enteties move so check it
for(Entity entity : _mapEntities) {
//get the corners
Rectangle bounds = entity.getBounds();
Block[] corners = getCornerBlocks(bounds);
Vector2d dir = entity.getDirection();

//moving down
if(dir.y > 0) {
if(corners[2].isSolid() || corners[3].isSolid()) {
Rectangle blkBounds = null;

if(corners[2].isSolid()) {
blkBounds = corners[2].getBounds();
} else {
blkBounds = corners[3].getBounds();
}

entity.setPositionY(blkBounds.y);
}
} else {
if(corners[0].isSolid() || corners[1].isSolid()) {
Rectangle blkBounds = null;

if(corners[0].isSolid()) {
blkBounds = corners[0].getBounds();
} else {
blkBounds = corners[1].getBounds();
}

entity.setPositionY(blkBounds.y + blkBounds.height + bounds.height);
}
}

bounds = entity.getBounds();
corners = getCornerBlocks(bounds);

//moving to the right
if(dir.x > 0) {
if(corners[1].isSolid() || corners[3].isSolid()) {
Rectangle blkBounds;

if(corners[1].isSolid()) {
blkBounds = corners[1].getBounds();
} else {
blkBounds = corners[3].getBounds();
}

entity.setPositionX(blkBounds.x - (bounds.width-entity.getCurrentSprite().getOffsetX())-1);
}
} else {
if(corners[0].isSolid() || corners[2].isSolid()) {
Rectangle blkBounds;

if(corners[0].isSolid()) {
blkBounds = corners[0].getBounds();
} else {
blkBounds = corners[2].getBounds();
}

entity.setPositionX(blkBounds.x + blkBounds.width + (bounds.width/2));
}
}

bounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height+1);
corners = getCornerBlocks(bounds);

//moving down
if(dir.y > 0) {
if(corners[2].isSolid() || corners[3].isSolid()) {
Rectangle blkBounds = null;

if(corners[2].isSolid()) {
blkBounds = corners[2].getBounds();
} else {
blkBounds = corners[3].getBounds();
}

entity.landed();
System.out.println("landed");
}
}
}
}

• Are you sure after you Detect a collision you are reacting to it? It sounds like the character is not being pushed back Out of the object and is instead being stopped where they are (but I did not read the code throughly). – James May 18 '11 at 0:09
• Yes I'm sure of it. The entity.setPositionX() or entity.setPositionY() is called after the collision check. When I walk against the wall without jumping then the player is pushed back correctly. – Sven van Zoelen May 18 '11 at 4:25

## 2 Answers

You sprite character gets snagged on the intersections between two tiles. See the following diagram, which depicts the situation at the start of your collision routine.

Mario was moved to the right due to player controls. This will place the sprite in the three tiles. Then, you perform the collision detection on downwards Y. Notice the point in the blue circle: this will detect a collision with the second block and tries to resolve it by setting the Y coordinate to the upper bound of that block, effectively suspending Mario in mid-air.

As soon as the player releases the key to move Mario to the right, the sprite will not move into the wall, and the point that previously would detect a collision, will not register a hit. This will allow Mario to fall to the ground.

The quick and dirty resolution to this problem is resolving X collisions first, and only apply gravity when the player is not on the ground. If you would keep the gravity, the same problem would occur when walking around (this is the 'trippy' behaviour you have seen already after swapping the X and Y check ;) ).

• Thanks! I swapped the x and y check and implemented a isOnLand check. So the player can jump and glide against the wall. Now another problem has come up, when the player stand next to the wall and jumps, then when he lands the x get adjusted (from the collision of the floor) and the player gets placed into the wall.. – Sven van Zoelen May 18 '11 at 16:45
• I find it hard to imagine the possible code structure based on your comment. Why does X need to be adjusted when Mario collides with the floor after a jump? I would think this will only need an adjustment in Y, leaving X untouched. – ghost May 18 '11 at 17:59

I have found out what I was doing wrong.

The key lies in when you update the position. What i did wrong was that I did the 'change positions first and then check and adjust' method, and I had to do it in the order, check and then update.

My previous code did the following:

• update entities locations with velocity.
• check for collisions and update the entities locations.

This resulted in that after the player jumped, Mario's location was changed in the update method so that the collision box would be in the ground tiles. Then the collision method changed the X position to the x of the left bottom corner and then the y would move it to above the ground tile. This means that Mario get shifted back a few pixels every time the player would land from a jump.

I have tried to swap the X and Y check methods, but then the problem just changes to something else (the wall hug).

Now the solution, I have changed it to the following:

• update entities velocity.
• collision method
• try to place the entity on the X with the velocity, if failed, change it as best as posible.
• try to place the entity on the Y with the velocity, if failed, change it as best as posible.

Now the X get checked/changed first and then the collision box is updated with the new entity location. Then the Y get checked/changed.

This works like a charm :)

• Can you give some examples? It's hard to understand what you did – gyozo kudor Aug 9 '14 at 22:15