Search code examples
javagridcollision-detectiontiles

Java: 2D world collision detection code bug


this is the second game that I'm having issues with in the same area... the first one I don't even know how I fixed it exactly. The coordinate system was also in doubles.

This is the "update" code for the physics class that will be inherited by all the objects in the world (players, enemies, items). All these objects will have to be able to obey collisions with the world, so... when they have a xSpeed or ySpeed this part of the code will run to apply and "snap" the object into the correct space if it hits a solid (dubbed state 2) world tile. Each world tile is 20x20 pixels.

Problem: It works perfectly fine except...

if you snap to the right tile, then you cannot move up or down. (though you can move left, and then move up or down)

if you snap to the bottom tile, then you cannot move left or right. (though you can move up and then move left or right)

    if(xSpeed<0){
        try{
            int dest = hitbox.x + (int)(xSpeed/20*tick);
            if(Ids.tiles[world[dest/20][hitbox.y/20]].getState() == 2 || Ids.tiles[world[dest/20][(hitbox.y+hitbox.width)/20]].getState() == 2){
                hitbox.x = (int)Math.ceil(dest/20.)*20;
                xSpeed = 0;
            }else{
                hitbox.x += (int)(xSpeed/20*tick);
            }
        }catch(Exception e){
            hitbox.x += (int)(xSpeed/20*tick);
        }
    }else if(xSpeed>0){
        try{
            int dest = hitbox.x+hitbox.width+(int)(xSpeed/20*tick);
            if(Ids.tiles[world[dest/20][hitbox.y/20]].getState() == 2 || Ids.tiles[world[dest/20][(hitbox.y + hitbox.width)/20]].getState() == 2){
                hitbox.x = dest/20*20 - hitbox.width;
                xSpeed = 0;
            }else{
                hitbox.x += (int)(xSpeed/20*tick);
            }
        }catch(Exception e){
            hitbox.x += (int)(xSpeed/20*tick);
        }
    }

    if(ySpeed<0){
        try{
            int dest =  hitbox.y + (int)(ySpeed/20*tick);
            if(Ids.tiles[world[hitbox.x/20][dest/20]].getState() == 2 || Ids.tiles[world[(hitbox.x + hitbox.width)/20][dest/20]].getState() == 2){
                hitbox.y = (int)Math.ceil(dest/20.)*20;
                ySpeed = 0;
            }else{
                hitbox.y +=  (int)(ySpeed/20*tick);;
            }
        }catch(Exception e){
            hitbox.y +=  (int)(ySpeed/20*tick);;
        }
    }else if(ySpeed>0){
        try{
            int dest = hitbox.y + hitbox.height +  (int)(ySpeed/20*tick);
            if(Ids.tiles[world[hitbox.x/20][dest/20]].getState() == 2 || Ids.tiles[world[(hitbox.x + hitbox.width)/20][dest/20]].getState() == 2){
                hitbox.y = dest/20*20 - hitbox.height;
                ySpeed = 0;
            }else{
                hitbox.y +=  (int)(ySpeed/20*tick);;
            }
        }catch(Exception e){
            hitbox.y +=  (int)(ySpeed/20*tick);;
        }
    }

Solution

  • Consider what happens on a right-edge (moving rightward) collision. This condition passes:

    Ids.tiles[world[dest/20][hitbox.y/20]].getState() == 2
    

    Suppose hitbox.width is 5. Then this will return true as soon as hitbox.x is 15. But you then reset hitbox.x to dest/20*20 - hitbox.width, which works out to be exactly where it already is. Then, in the vertical direction, if the object moves then its rightmost edge is already colliding with tiles, so it can't move.

    To fix this, subtract 1 pixel from the hitbox size when using it to look up grid positions — before the division, of course.

    If you were working in floating-point coordinates, then you would use Math.ceil((hitbox.x + hitbox.width)/20.0) - 1 to produce a similar effect without having an arbitrary epsilon.