Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I'm working on a tile-based Mario clone. I have implemented the tile based collision method from the tutorial http://www.tonypa.pri.ee/tbw/tut05.html.

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.

Here's a image of the collision box of the player.

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");
                }
            }
        }
    }
share|improve this question
    
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 May 18 '11 at 4:25
add comment

2 Answers 2

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.

Collision with a wall

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 ;) ).

share|improve this answer
    
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 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
add comment
up vote 0 down vote accepted

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 :)

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.