Question

I am making a 2d rpg game in java and I have run into a problem. I can make the player move around the stage and I have rocks, trees, walls, etc. on the stage as well. I don't know how to detect the collision and make it to where the player can't move through the object. The code that reads map file and draws image on the canvas is as follows:

public void loadLevel(BufferedImage levelImage){
    tiles = new int[levelImage.getWidth()][levelImage.getHeight()];
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            Color c = new Color(levelImage.getRGB(x, y));
            String h = String.format("%02x%02x%02x", c.getRed(),c.getGreen(),c.getBlue());

            switch(h){
            case "00ff00"://GRASS Tile - 1
                tiles[x][y] = 1;
                break;
            case "808080"://Stone -2
                tiles[x][y] = 2;
                break;
            case "894627"://Dirt -3
                tiles[x][y] = 3;
                break;
            case "404040"://Rock on Grass -4
                tiles[x][y] = 4;
                break;
            case "00b700"://Tree -5
                tiles[x][y] = 5;
                break;
            case"000000"://Wall -6
                tiles[x][y] = 6;
                break;
            case "cccccc"://Rock on stone -7
                tiles[x][y] = 7;
                break;
            default:
                tiles[x][y] = 1;
                System.out.println(h);
                break;

            }
        }
    }
}

And the player class is as follows:

public class Player {

private int x,y;
public int locx,locy;
private Rectangle playerR;

private ImageManager im;
public boolean up =false,dn = false,lt=false,rt=false,moving = false,canMove = true;
private final int SPEED =2;

public Player(int x, int y, ImageManager im){
    this.x = x;
    this.y = y;
    this.im = im;
    locx = x;
    locy = y;
    playerR = new Rectangle(x,y,16,16);
}                                       

public void tick(){
    if (up) {
        if(canMove){
        y -= SPEED;
        locx = x;
        locy = y;
        playerR.setLocation(locx, locy);
        moving = true;
        }
        else{
            y += 1;
            canMove=true;
        }
    }
    if (dn) {

        y +=SPEED;
        locx = x;
        locy = y;
        moving = true;
        }
    }
    if (lt) {
        x -= SPEED;
        locx = x;
        locy = y;
        moving = true;

    }
    if (rt) {
        x+=SPEED;
        locx = x;
        locy = y;
        moving = true;
        }
    }
    if(moving){
        System.out.println("PLAYER\tX:"+locx+" Y:"+locy);
        moving = false;
    }
}

public void render(Graphics g){
    g.drawImage(im.player, x, y, Game.TILESIZE*Game.SCALE, Game.TILESIZE*Game.SCALE, null);
}   

}

I don't really know how to do collision, but i googled it and people said to make a rectangle for the player and all the objects that the player should collide with, and every time the player moves, move the player's rectangle. Is this the right way to do this?

EDIT EDIT EDIT EDIT

code for when collision is true:

if (rt) {
        x+=SPEED;
        locx = x;
        locy = y;
        playerR.setLocation(locx, locy);
        for(int i = 0;i<Level.collisions.size();i++){
            if(intersects(playerR,Level.collisions.get(i))==true){
                x-=SPEED;
                locx = x;
                playerR.setLocation(locx, locy);
            }
        }
        moving = true;
    }

And the intersects method is as follows:

private boolean intersects(Rectangle r1, Rectangle r2){
    return r1.intersects(r2);
}
Was it helpful?

Solution

I'm going to focus on your tick method since that is where most of this logic is going. There are a couple changes here. Most notably, we only move the rectangle before checking for collisions. Then loop through all the collideable objects in your level. Once one is found, we reset our x and y and break out of the loop (no sense in looking at any of the other objects since we already found the one we collided with). Then we update our player position. By doing it this way, I centralized the code so it is not being repeated. If you ever see yourself repeating code, there is a pretty good chance that it can be pulled out to a common place, or to a method.

public void tick() {

    if (up) {
        y -= SPEED;
    } else if (dn) {
        y += SPEED;
    } else if (lt) {
        x -= SPEED;
    } else if (rt) {
        x += SPEED;
    }

    playerR.setLocation(x, y);

    for (Rectangle collideable : Level.collisions) {
        if (intersects(playerR, collideable)) {
            x = locx;
            y = locy;
            playerR.setLocation(x, y);
            break;
        }
    }

    locx = x;
    locy = y;

}

OTHER TIPS

There are different ways to do that. As you talk about a "rpg" i think your view is Isometric (45° top down).
I would do the collision detection in pure 90° top down, as it is easier and, imho, more realistic.
We have 2 possibilities:

  1. Move your Player to the next position. If there is a collision, reset his position.
  2. Calculate the next position, if there would be a collision don't move.

If you want to have a "gliding" collision response, you have to check in which axis the collision will happen, and stop / reset movement for this axis only.

To have a more efficient collision detection only check near objects, which will possibly collide. Do this by comparing a squared "dangerRadius" with the squared distance between your player and the object:

if ((player.x - object.x)² + (player.y - object.y)² <= dangerRadius²)
     // Check for intersection

This will sort out most of the objects by using a simple calculation of:

  • 2 subtractions
  • 1 addition
  • 3 multiplications (the ²)
  • 1 compare (<=)

In your game you should sepparate the logic and the view. So basicly you don't detect, if the two images overlapp, but you check, if the objects in your logic overlap. Then you draw the images on the right position.

Hope this helps.

EDIT: Important: If you update your character, depending on the time between the last and this frame (1/FPS) you have to limit the max timestep. Why? Because if for some reason (maybe slow device?) the FPS are really low, it is possible, that the character moves verry far between 2 frames and for that he could go through an object in 1 frame.
Also if you simply reset the movement on collision or just don't move the distance between you and the object could be big for low FPS. For normal FPS and not to high movementspeed this won't happen/ be noticeable.

I personally am fairly new to Java, though I have worked with C# in the past. I am making a similar game, and for collision detection I just check the locations of the player and objects:

if (z.gettileX() == p.gettileX()){
                if (z.gettileY() == p.gettileY()){
    System.out.println("Collision!");
        }
    }

If the player (p) has equal X coordinates and Y coordinates to z(the bad guy), it will send this message and confirm that the two have, in fact, collided. If you can make it inherent in the actual class behind z to check if the coordinates a equal, you can create an unlimited number of in-game objects that detect collision and react in the same way, i.e. walls.

This is probably what your looking for. I've made this class spicificly for collision of multiple objects and for individual side collisions.

abstract class Entity {

    private Line2D topLine;
    private Line2D bottomLine;
    private Line2D leftLine;
    private Line2D rightLine;
    private Rectangle rectangle;

    private Entity entity;

    protected boolean top;
    protected boolean bottom;
    protected boolean left;
    protected boolean right;

    protected int x;
    protected int y;

    protected int width;
    protected int height;

    public Entity(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        updateLinesAndRects();
    }

    public void updateLinesAndRects() {
        topLine = new Line(x + 1, y, width - 2, 0);
        bottomLine = new Line(x + 1, y + height, width - 2, height);
        leftLine = new Line(x, y + 1, 0, height - 2);
        rightLine = new Line(x + width, y + 1, 0, height - 2);

        rectangle = new Rectangle(x, y, width, height)
    }

    public void setCollision(Entity entity) {
        this.entity = entity;
        top = isColliding(new Line2D[]{topLine, bottomLine, leftLine, rightLine});
        bottom = isColliding(new Line2D[]{bottomLine, topLine, leftLine, rightLine});
        left = isColliding(new Line2D[]{leftLine, topLine, bottomLine, rightLine});
        right = isColliding(new Line2D[]{rightLine, topLine, bottomLine, leftLine});
    }

    public void updateBounds() {
        if(top) y = entity.y + entity.height;
        if(bottom) y = entity.y - height;
        if(left) x = entity.x + entity.width;
        if(right) x = entity.x - width;
    }

    public boolean isColliding() {
        return rectangle.intersects(entity.rect);
    }

    private boolean isLinesColliding(Line2D[] lines) {
        Rectangle rect = entity.getRectangle();
        return lines[0].intersects(rect) && !lines[1].intersects(rect) && !lines[2].intersects(rect) && !lines[3].intersects(rect);
    }

    private Line2D line(float x, float y, float width, float height) {
        return new Line2D(new Point2D.Float(x, y), new Point2D.Float(x + width, x + height));
    }

    public Rectangle getRectangle() {
        return rectangle;
    }
}

Example:

class Player extends Entity{

    Entity[] entities;

    public Player(int x, int y, int width, int height) {
        super(x, y, width, height);
    }

    public void update() {
        updateLinesAndRects();
        for(Entity entity : entities) {
            setCollision(entity);
            if(top) system.out.println("player is colliding from the top!");
            if(isColliding()) system.out.println("player is colliding!");
            updateBounds(); // updates the collision bounds for the player from the entities when colliding.
        }
    }

    public void setEntities(Entity[] entities) {
        this.entities = entities;
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top