1
votes

I am writing an algorithm for collision detection in slick2d + java that I will eventually use in a platformer game that I will be making. How the algorithm works is it detects how much the player is overlapping a rectangle and then moves the player out of the rectangle by that overlap. The problem is, the algorithm has quite a few issues that I can't figure out how to fix. First, sometimes the player move too far out of the rectangle so it looks it is bouncing off it. Secondly sometimes the player is able to move a small but noticeable amount inside of the rectangle. Lastly, if I increase the velocity sometimes the player can pass all the way through the rectangle. This is quite a vague question but I really need some help figuring out what is wrong. Any ideas at all would be greatly appreciated. The source code should compile without any problems if you have Slick2D installed.

Algorithm:

public void Collision(Polygon player, Polygon poly, Vector2f translation){
    Vector2f magnitude = new Vector2f();

    //Find the vectre of each object
    Vector2f p1Centre = new Vector2f(player.getX() + (player.getWidth()/2), player.getY() + (player.getHeight()/2));
    Vector2f p2Centre = new Vector2f(poly.getX() + (poly.getWidth()/2), poly.getY() + (poly.getHeight()/2));

    //Calculate the distance between the two
    Vector2f distance = new Vector2f(p1Centre);
    distance.sub(p2Centre);

    //Get the absolute distance
    Vector2f absDistance = new Vector2f(distance.x<0 ? -distance.x : distance.x, distance.y<0 ? -distance.y : distance.y);

    //Get the combined half widths and heights of each object
    Vector2f halvedBounds = new Vector2f((player.getWidth() + poly.getWidth())/2.0f, (player.getHeight() + poly.getHeight())/2.0f);

    //If the absolute distance is less thate the halved widths heights then there is a collision
    if((absDistance.x < halvedBounds.x) && (absDistance.y < halvedBounds.y)){

        //Set the magnitude vector to the halved bounds minus the absolute distance
        magnitude.x = halvedBounds.x - absDistance.x;
        magnitude.y = halvedBounds.y - absDistance.y;

        //Only react to the lesser overlap;
        if(magnitude.x < magnitude.y){
            magnitude.x = (distance.x > 0) ? magnitude.x : -magnitude.x;
            magnitude.y = 0;
        }
        else{
            magnitude.y = (distance.y > 0) ? magnitude.y : -magnitude.y;
            magnitude.x = 0;
        }

        //Debug
        System.out.println(magnitude.x+"            "+magnitude.y);
        System.out.println(translation.x+"            "+translation.y+"\n");

        //Add the magnitude to the player position
        position.add(magnitude);
    }       
}

Full Source:

import java.util.ArrayList;

import org.newdawn.slick.AppGameContainer;
import org.newdawn.slick.BasicGame;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Polygon;
import org.newdawn.slick.geom.Vector2f;

public class TestCode extends BasicGame {

    private Vector2f position = new Vector2f(300, 300);

    private ArrayList<Polygon> solids;
    private Polygon player;

    public TestCode(String title) {
        super(title);
    }

    public static void main(String[] args) throws SlickException{
        AppGameContainer game = new AppGameContainer(new TestCode("test"));
        game.setDisplayMode(800, 600, false);
        game.start();
    }

    @Override
    public void render(GameContainer gc, Graphics g) throws SlickException {

        if(gc.isPaused()){
            g.setColor(Color.red);
            g.drawString("Paused", 90, 10);
        }else{
            g.setColor(Color.green);
            g.drawString("Playing", 90, 10);
        }

        g.setColor(Color.red);
        for(Polygon p : solids)
            g.fill(p);

        g.setColor(Color.cyan);
        g.fill(player);

    }

    @Override
    public void init(GameContainer gc) throws SlickException {      
        gc.setVSync(true);

        solids = new ArrayList<Polygon>();

        player = new Polygon(new float[]{
                50, 50,  // upper left point
                70, 50,  // upper right
                70, 90, // lower right
                50, 90   // lower left
        }); 


        for(int i=0, x=200, y=200; i<10; i++, x+=40){
            solids.add(new Polygon(new float[]{
                    x, y,  // upper left point
                    x+40, y,  // upper right
                    x+40, y+40, // lower right
                    x, y+40   // lower left
            }));    
        }

    }

    @Override
    public void update(GameContainer gc, int delta) throws SlickException {

        Input input = gc. getInput();

        Vector2f translation = new Vector2f(0, 0);

        if(input.isKeyDown(Input.KEY_UP))
            translation.y = -1f;
        if(input.isKeyDown(Input.KEY_DOWN))
            translation.y = 1f;
        if(input.isKeyDown(Input.KEY_LEFT))
            translation.x = -1f;
        if(input.isKeyDown(Input.KEY_RIGHT))
            translation.x = 1f;

        translation.normalise();
        translation.x*=2;
        translation.y*=2;

        position.add(translation);

        for(Polygon p : solids)
            Collision(player, p, translation);

        player.setLocation(position);
    }

    public void Collision(Polygon player, Polygon poly, Vector2f translation){
        Vector2f magnitude = new Vector2f();

        //Find the vectre of each object
        Vector2f p1Centre = new Vector2f(player.getX() + (player.getWidth()/2), player.getY() + (player.getHeight()/2));
        Vector2f p2Centre = new Vector2f(poly.getX() + (poly.getWidth()/2), poly.getY() + (poly.getHeight()/2));

        //Calculate the distance between the two
        Vector2f distance = new Vector2f(p1Centre);
        distance.sub(p2Centre);

        //Get the absolute distance
        Vector2f absDistance = new Vector2f(distance.x<0 ? -distance.x : distance.x, distance.y<0 ? -distance.y : distance.y);

        //Get the combined half widths and heights of each object
        Vector2f halvedBounds = new Vector2f((player.getWidth() + poly.getWidth())/2.0f, (player.getHeight() + poly.getHeight())/2.0f);

        //If the absolute distance is less thate the halved widths heights then there is a collision
        if((absDistance.x < halvedBounds.x) && (absDistance.y < halvedBounds.y)){

            //Set the magnitude vector to the halved bounds minus the absolute distance
            magnitude.x = halvedBounds.x - absDistance.x;
            magnitude.y = halvedBounds.y - absDistance.y;

            //Only react to the lesser overlap;
            if(magnitude.x < magnitude.y){
                magnitude.x = (distance.x > 0) ? magnitude.x : -magnitude.x;
                magnitude.y = 0;
            }
            else{
                magnitude.y = (distance.y > 0) ? magnitude.y : -magnitude.y;
                magnitude.x = 0;
            }

            //Debug
            System.out.println(magnitude.x+"            "+magnitude.y);
            System.out.println(translation.x+"            "+translation.y+"\n");

            //Add the magnitude to the player position
            position.add(magnitude);
        }       
    }   
}
2

2 Answers

2
votes

The issue with a high velocity and the player passing through the rectangle has to do with how often you're sampling the collision data.

Lets suppose that I'm really dumb, and I only check for collisions once a second. If an object is moving at 15 meters per second, and there's a 1 meter square in its way. If I check for collisions when the object is 7 meters away from the square, and then one second later, I'll completely miss that the object went through the square.

The way a lot of collision detection libraries deal with this is they have fast moving objects be checked more often than regular objects. How "fast" is your player moving when this happens?

2
votes

So just turn your problem around: check before moving, not after. If the player would land in the rectangle don't allow him.

And here comes the more important part of your problem: how far is "just touching the surface" (and really depends on how you do your collision detection; if you are doing bounding-box collisions it translates into a calculation, if you are doing pixel-perfect collisions you might have to test a few what-if cases).

Since this happens all the time, try to avoid unnecessary work (why test pixel by pixel, interval splitting might be better approach; do bounding box calculations first, pixel perfect then; for "rectangular enough" objects bounding box ~~ pixel pirfect ...)

Welcomme to the world of programming, it's more about solving problems, not so much about slapping a buch of "paint a spire, move it by x pixels & goto 10" statements together ;-)