Search code examples
javacollision-detectiongraphics2dshapes

Collision detection only working on top side of wall - Java


I have an assignment to create a 2D game, this game must use an abstract class Shape which is used to draw shapes. I decided to make a 2D platformer, but it's my first time doing something like this. I am trying to implement collision detection, and decided to create an offset of my player object to see if it collides with my rectangle object, and stop movement if it does. This is only working for the top side, however on the right, left and botton the player will move through the rectangle and I don't understand why. I expected the collision detection to work for all sides. I would like to know how I can change my collision detection to work for all sides of the rectangle.

Here's a gif showing what happens: enter image description here

My App class:

package A2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.util.TreeSet;

import static java.awt.event.KeyEvent.*;

public class App extends JFrame {

    private static final int GRAVITY = 10;
    private static final int MAX_FALL_SPEED = 30;

    public App() {

        final Player player = new Player(200, 300);
        final Rectangle rectangle = new Rectangle(100, 400);
        JPanel mainPanel = new JPanel() {
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                player.draw(g);
                rectangle.draw(g);
            }
        };

        mainPanel.addKeyListener(new controlHandler(this, player, rectangle));
        mainPanel.setFocusable(true);
        add(mainPanel);

        setLayout(new GridLayout());
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setSize(600, 600);
    }


    class controlHandler implements ActionListener, KeyListener {
        int keyCode;
        App app;
        Player player;
        Rectangle rectangle;
        TreeSet<Integer> keys = new TreeSet<>();
        Timer time = new Timer(5, this);
        boolean collision = false;


        public controlHandler(App app, Player player, Rectangle rectangle) {
            this.app = app;
            this.player = player;
            this.rectangle = rectangle;
            time.start();
        }

        public void actionPerformed(ActionEvent e) {
            player.x += player.xVelocity;
            player.y += player.yVelocity;

            Rectangle2D offset = player.getOffsetBounds();

            if(offset.intersects(this.rectangle.wallObj.getBounds2D())){
                collision = true;
                player.xVelocity = 0;
                player.yVelocity = 0;
            }
            else collision = false;

            repaint();
        }

        public void keyPressed(KeyEvent e) {
            keyCode = e.getKeyCode();
            keys.add(keyCode);


            switch (keyCode) {
                case VK_UP:
                    moveUp();
                    break;
                case VK_DOWN:
                    moveDown();
                    break;
                case VK_LEFT:
                    moveLeft();
                    break;
                case VK_RIGHT:
                    moveRight();
                    break;
            }
        }

        public void keyReleased(KeyEvent e) {
            released();
        }
        public void keyTyped(KeyEvent e) { }

        public void moveUp() {
            player.xVelocity = 0;
            player.yVelocity = -2;
        }

        public void moveDown() {
            if(!collision) {
                player.xVelocity = 0;
                player.yVelocity = 2;
            }
        }

        public void moveLeft() {
            player.xVelocity = -2;
            player.yVelocity = 0;
        }

        public void moveRight() {
            player.xVelocity = 2;
            player.yVelocity = 0;
        }

        public void released() {
            player.xVelocity = 0;
            player.yVelocity = 0;
        }
    }

    public static void main(String[] args) {
        App app = new App();
        app.setVisible(true);
    }
}

abstract class Shape {
    public double x;
    public double y;

    public void draw(Graphics g) { }
}

Player class:

package A2;

import java.awt.*;
import java.awt.geom.Rectangle2D;

public class Player extends Shape {
    public double xVelocity;
    public double yVelocity;
    public double length = 60;
    public double top;
    public double right;
    public double left;
    public double bottom;
    public  Rectangle2D playerObj;


    public Player(double x, double y) {
        this.x = x;
        this.y = y;
        playerObj = new Rectangle2D.Double(x, y, length, length);
    }

    public Rectangle2D getOffsetBounds(){
        return new Rectangle2D.Double(x + xVelocity , y + yVelocity , length, length);
    }

    public void draw(Graphics g) {
        playerObj = new Rectangle2D.Double(x, y, length, length);
        g.setColor(Color.BLACK);
        Graphics2D g2 = (Graphics2D)g;
        g2.fill(playerObj);

    }
}

Rectangle class (will be a platform):

package A2;

import java.awt.*;
import java.awt.geom.Rectangle2D;

public class Rectangle extends Shape {
    public double width = 400;
    public double height = 30;
    public double top;
    public double right;
    public double left;
    public double bottom;
    public Rectangle2D wallObj;

    public Rectangle(double x, double y) {
        this.x = x;
        this.y = y;
        wallObj = new Rectangle2D.Double(x, y, width, height);
    }

    public void draw(Graphics g) {
        g.setColor(Color.ORANGE);
        Graphics2D g2 = (Graphics2D)g;
        g2.fill(wallObj);

    }
}

Solution

  • So, based on my understanding, your collision detection process is basically to create a "virtual" instance of the object and determine if it will collide.

    Your code is actually work, you can tell by the fact that the object "stops" moving once it collides, but what you're not doing, is reseting the objects position after it.

    Let's look at the original code...

    public void actionPerformed(ActionEvent e) {
      player.x += player.xVelocity;
      player.y += player.yVelocity;
    
      Rectangle2D offset = player.getOffsetBounds();
    
      if (offset.intersects(this.rectangle.wallObj.getBounds2D())) {
        collision = true;
        player.xVelocity = 0;
        player.yVelocity = 0;
      } else {
        collision = false;
      }
    
      repaint();
    }
    
    • Update the position of the object
    • Create a virtual instance of the object, which applies the velocity to the objects current position ... again?
    • Determine if the object collides
    • Stop the movement if any
    • ... where do you reset the position of object so it's no longer colliding?!?

    Instead, you shouldn't set the position of the object until AFTER you've determine if a collision has occurred or not, for example...

    public void actionPerformed(ActionEvent e) {
    
      Rectangle2D offset = player.getOffsetBounds();
    
      if (offset.intersects(this.rectangle.wallObj.getBounds2D())) {
        collision = true;
        player.xVelocity = 0;
        player.yVelocity = 0;
      } else {
        player.x += player.xVelocity;
        player.y += player.yVelocity;
        collision = false;
      }
    
      repaint();
    }
    

    The problem with this approach, though, is if the velocity is large enough, the object will appear not to collide, but stop a little before it, as the amount of change is larger than the remaining gap.

    What you need to do, is once you've detected a collision is calculate the difference between the amount you want to move and the space available and move the object only by that amount instead