Search code examples
javacollision-detectionslick2d

Solving 2d game collision (polygons)


I saw a lot of tutorials how to detect collision, but not how to solve it. I am making a top down game, player has circle collision shape, while walls are various polygons.
I am using slick2d. What I should do, is that if player collides with a corner, I move player by a normal until he does not collide with it. After I check corners, I check edges in similar way. However my player is often shaking, snapping to corners, occasionally going through walls.

Here is the code:

The collision check code (inside player class):

public void checkCollision (ArrayList<Wall> walls) {
    //pos is position vector
    Shape player_c = getShape();
    for (Wall w : walls){
        if (player_c.intersects(w.getShape())){
            for (int i = 0; i < w.getShape().getPointCount(); i++){
                float point_x, point_y;
                point_x = w.getShape().getPoint(i)[0];
                point_y = w.getShape().getPoint(i)[1];
                if (player_c.contains(point_x, point_y)){
                    while (player_c.intersects(w.getShape())){
                        float normal_x, normal_y;
                        normal_x = w.getShape().getNormal(i)[0];
                        normal_y = w.getShape().getNormal(i)[1];
                        pos.x += 0.001 * normal_x;
                        pos.y += 0.001 * normal_y;
                        player_c = getShape();
                    }
                    break;
                } else {
                    if (player_c.intersects(PolygonExt.getLine((Polygon)w.getShape(), i))){
                        while (player_c.intersects(w.getShape())){
                            float[] normal;
                            normal = PolygonExt.getLineNormal((Polygon)w.getShape(), i );
                            pos.x += 0.001 * normal[0];
                            pos.y += 0.001 * normal[1];
                            player_c = getShape();
                        }
                        break;
                    }
                }
            }
        }
    }
}

Wall class:

public class Wall {

Polygon shape;
private Color color;

public Wall(Vector2f... points) {
    shape = new Polygon();
    for (int i = 0; i < points.length; i++){
        shape.addPoint(points[i].x, points[i].y);
    }
    color = new Color((float)Math.random()*0.5f + 0.5f, (float)Math.random()*0.5f + 0.5f, (float)Math.random()*0.5f + 0.5f);
}

public static ArrayList<Wall> createMap(char[][] map, int wall_length) {
    ArrayList<Wall> w = new ArrayList<>();
    int width = map[0].length;
    int height = map.length;
    System.out.println(width + " " + height);
    for (int i = 0; i < width; i++){
        for (int j = 0; j < height; j++){
            if (map[j][i] == 'x'){
                w.add(new Wall (new Vector2f(i*wall_length, j*wall_length), new Vector2f((i+1)*wall_length, j*wall_length)
                               ,new Vector2f((i+1)*wall_length, (j+1)*wall_length), new Vector2f(i*wall_length, (j+1)*wall_length)));
            }
        }
    }
    return w;
}

public void update (float d){

}

public void render (Graphics g){
    g.setColor (color);
    g.fill(shape);  

}

public Shape getShape () {
    return shape;
}
}

PolygonExt class:

public class PolygonExt extends Polygon {

public static float[] getLineNormal (Polygon p, int index){
    float[] result = new float[2];

    float x1, x2, y1, y2;
    int next_index = (index + 1) % p.getPointCount();

    x1 = p.getPoint(index)[0];
    y1 = p.getPoint(index)[1];
    x2 = p.getPoint(next_index)[0];
    y2 = p.getPoint(next_index)[1];

    double angle = Math.atan2(y2-y1, x2-x1)+Math.PI/2d;
    result[0] = (float) Math.cos(angle);
    result[1] = (float) Math.sin(angle);

    if (p.contains(x1+(x2-x1)/2 + result[0]*0.01f, y1+(y2-y1)/2 + result[1]*0.01f)){
        result[0] *= -1;
        result[1] *= -1;
    }
    return result;
}

public static Line getLine (Polygon p, int index){
    int next_index = (index + 1) % p.getPointCount();
    float x1, x2, y1, y2;
    x1 = p.getPoint(index)[0];
    y1 = p.getPoint(index)[1];
    x2 = p.getPoint(next_index)[0];
    y2 = p.getPoint(next_index)[1];
    Line l = new Line (x1, y1, x2, y2);
    return l;
}
}

Solution

  • In your player class you start by testing for an intersection with the walls with this line:

    if (player_c.intersects(w.getShape())){

    Then within that if section, it looks as though you are updating the player's position until it stops intersecting with the walls here:

    while (player_c.intersects(w.getShape())){
        normal_x, normal_y;
        w.getShape().getNormal(i)[0];
        w.getShape().getNormal(i)[1];
        x += 0.001 * normal_x;
        pos.y += 0.001 * normal_y;
        player_c = getShape();
    }
    

    What this suggests is that all of the player movement when interacting with walls will happen inside this loop before Slick2D performs its next render() cycle. This means that any player-wall interaction would appear to happen instantly, which presumably is why it looks like the player jumps around.

    For the incremental movements to appear on-screen you would need to update the player position incrementally per update() cycle (i.e. remove the while loop above as a starting point) in Slick2D so that each incremental change can then be rendered to the screen in the following render() cycle. You should also build in the use of the time delta so that the movements appear smooth irrespective of how quickly the game is running.

    In terms of why the player is jumping through walls, that suggests there is something wrong with your collision detection logic. Have you thought about using a physics engine to do this for you? It would save you writing a set of libraries that already exist elsewhere and that are designed to deal with all aspects of collision detection. I started out trying to write my own collision detection routines, but once I started reading about how to do this properly (here is one of many websites outlining the basics - this has a section on circle and axis aligned bounding box [AABB] interactions, which I think covers your scenario) I quickly realised that it was a huge undertaking and it would be better to use a library. JBox2D is one example, which seems to be quite popular. I use dyn4j, which works well although has quite a small user community.

    Hope that helps.