Collision bug in Java

XXXconanXXX

Cocktails anyone?
Reaction score
284
Long time no see, eh?

Got a question for you guys in this fine establishment. I've been working on a very simple, bare-bones 2D shooting game in Java that's reminiscent of Pong, and am having a rather unsightly bug with the collision detection.

Seems that whenever either player gets any closer than about 30 pixels from the bullet about to hit, they still get hit. Odd thing is, it only happens when the player is below the bullet and not above.

Here's the code:

Shooter.java
Code:
//x coordinates starts at left side and increases to the right, y coordinates start at top and increase going down

import javax.swing.JFrame;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.util.concurrent.CopyOnWriteArrayList;
 
public class Shooter extends JFrame implements KeyListener {
//double buffer vars
private Graphics bufferGraphics;
private Image offscreen;
//end double buffer vars
private Player player1;
private Player player2;
public boolean player1Up = false;
public boolean player1Down = false;
public boolean player2Up = false;
public boolean player2Down = false;
CopyOnWriteArrayList<Bullet> bullets = new CopyOnWriteArrayList<Bullet>();
//Used solely for displaying player winnings
private String win1 = "Player 1 has won the game!";
private String win2 = "Player 2 has won the game!";
private int length = win1.length();


    public Shooter() {
        setTitle("2D Java Shooter");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setSize(400,800);
        setLocation(100,100);
        setBackground(Color.BLACK);
        setVisible(true);
        addKeyListener(this);
        
        player1 = new Player(20, (getHeight()/2)-20, 20, 90, 10);
        player2 = new Player(getWidth()-40, (getHeight()/2)-20, 20, 90, 10);
    }
    
    public void paint(Graphics g) { //All used for double buffering
        offscreen = createImage(getWidth(),getHeight()); 
        bufferGraphics = offscreen.getGraphics();
         
        paintComponent(bufferGraphics);
        g.drawImage(offscreen,0,0,null); 
        repaint();
    }
    public void paintComponent(Graphics g) { //Actual drawing goes here
        if (player1.getHealth() > 0 && player2.getHealth() >0) {
            for (Bullet bullet : bullets) {
                bullet.draw(g);
                bullet.update(this, 0);
            }
        } else if (player1.getHealth() == 0) {
            g.setColor(Color.WHITE);
            g.drawString(win2,125,400);
            
        } else if (player2.getHealth() == 0) {
            g.setColor(Color.WHITE);
            g.drawString(win1,125,400);
        }
        player1.draw(g);
        player1.update(this, 1);
        player2.draw(g);
        player2.update(this, 2);
        
        g.drawString("P1 HEALTH: "+player1.getHealth(),20,50);
        g.drawString("P2 HEALTH: "+player2.getHealth(),getWidth()-100,50);
    }   
    
    @Override
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_UP) { //player 2
            player2Up = true;
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            player2Down = true;
        } else if (e.getKeyCode() == KeyEvent.VK_W) { //player 1
            player1Up = true;
        } else if (e.getKeyCode() == KeyEvent.VK_S) {
            player1Down = true;
        }
    }
    @Override
    public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_UP) { //player 2
            player2Up = false;
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            player2Down = false;
        } else if (e.getKeyCode() == KeyEvent.VK_W) {
            player1Up = false;
        } else if (e.getKeyCode() == KeyEvent.VK_S) {
            player1Down = false;
        } else if (e.getKeyCode() == KeyEvent.VK_SPACE) { //Player 1 shooting
            Bullet player1Bullet = new Bullet(player2, 1, player1.getXPos()+20,player1.getYPos()+45,6,4,0);
            bullets.add(player1Bullet);
        } else if (e.getKeyCode() == KeyEvent.VK_ENTER) { //Player 2 shooting
            Bullet player2Bullet = new Bullet(player1, -1, player2.getXPos()-4,player2.getYPos()+45,6,4,0);
            bullets.add(player2Bullet);
        }
    }
    @Override
    public void keyTyped(KeyEvent e) {
    }
    
    public static void main(String[] args) {
    	new Shooter();
    }
}

Bullet.java
Code:
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Rectangle;

public class Bullet extends GameObject {
private int deltaX; //Determines which way the bullets shooting, 1 to go right and = -1 to go left
private Player player;
    public Bullet(Player player, int deltaX, int xPos, int yPos, int width, int height, int health) {
        this.player = player;
        this.deltaX = deltaX;
        this.xPos = xPos;
        this.yPos = yPos;
        this.width = width;
        this.height = height;
        this.health = health;
        this.rect = new Rectangle(xPos, yPos, width, height);
    }
    
    @Override
    public void draw(Graphics g) {
        g.setColor(Color.WHITE);
        g.fillRect(xPos, yPos, width,height);
    }
    
    @Override
    public void update(Shooter shooter, int id) {
        if(rect.intersects(player.rect)) { //collision detection for bullets
            player.setHealth(player.getHealth()-1);
            shooter.bullets.remove(this);
        } else if (xPos < 1 || xPos > 390) {
            shooter.bullets.remove(this);
        } else {
            xPos += deltaX;
            rect.x += deltaX;
        }
    }
    
}

Player.java
Code:
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Rectangle;

public class Player extends GameObject {

    public Player(int xPos, int yPos, int width, int height, int health) {
        this.xPos = xPos;
        this.yPos = yPos;
        this.width = width;
        this.height = height;
        this.health = health;
        this.rect = new Rectangle(xPos, yPos, width, height);
    }
    
    @Override //instructs compiler that we intend to override superclass (This case, GameObject)
    public void draw(Graphics g) {
        g.setColor(Color.WHITE);
        g.fillRect(xPos, yPos, width,height);
    }
    
    @Override
    public void update(Shooter shooter, int id) {
        if(id ==1){
            if(shooter.player1Up) {
                if(!(yPos < 27)){
                    yPos = yPos-4;
                    rect.y--;
                }
            } else if(shooter.player1Down) {
                if(!(yPos > shooter.getHeight() - 96)){
                    yPos = yPos+4;
                    rect.y++;
                }
            }
        }else if (id == 2){
            if(shooter.player2Up) {
                if(!(yPos < 27)){
                    yPos = yPos-4;
                    rect.y--;
                }
            } else if(shooter.player2Down) {
                if(!(yPos > shooter.getHeight() - 96)){
                    yPos = yPos+4;
                    rect.y++;
                }
            }
        }
    }
    
}

GameObject.java
Code:
import java.awt.Graphics;
import java.awt.Rectangle;

public abstract class GameObject {
protected Rectangle rect;
protected int xPos;
protected int yPos;
protected int width;
protected int height;
protected int health;

    abstract void draw(Graphics g);
    
    void update(Shooter shooter, int id) {
    }
    
    public int getXPos() {
        return xPos;
    }
    public void  setXPos(int pos) {
        this.xPos = pos;
    }
    
    public int getYPos() {
        return yPos;
    }
    public void  setYPos(int pos) {
        this.yPos = pos;
    }
    
    public int getWidth() {
        return width;
    }
    public void  setWidth(int width) {
        this.width = width;
    }
    
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    
    public int getHealth() {
        return health;
    }
    
    public void  setHealth(int health) {
        this.health = health;
    }
    
}

Any ideas on what may be causing this? I've been scouring the code for quite some time and have tried a multitude of techniques but nothing seems to be alleviating the problem.

The collision detection for the bullets is nested in the Bullet.update method, and there seems to be no discernible reason as to what is causing this. Is it because of the use of a Rect object to draw the players/bullets? Should I delete that and simply stick with the fillRect/clearRect methods and a slightly more convoluted collision detection system by cross-checking the coordinates of the bullets and players for added precision?
 

phyrex1an

Staff Member and irregular helper
Reaction score
446
In the Bullet class you always update rect.x and xPos by the same amount, but in Player you update rect.y by 1 while you update yPos by 4 units. That looks pretty weird to me, without any experience of awt.
Related: Why are you using two methods to keep track of game object positions (both the rect and xPos/yPos)?
 

XXXconanXXX

Cocktails anyone?
Reaction score
284
Keen eye rex. Changing the rect.y coordinates to +/-4 alleviated the problem.

Using two methods for game object positions was simply so the collision detection could be more streamlined and easier to process. Though I should probably take the longer route for good programming practice, and as I'll need it in upcoming projects. Do you advise against using the two methods?
 

phyrex1an

Staff Member and irregular helper
Reaction score
446
Do you advise against using the two methods?
Yes, and no.

As it is currently implemented most definitely yes. You should wrap up the position modifying code in a single place (for example in the GameObject, where you currently only uptades the x/yPos values), that way bugs like this are much less likely. Furthermore, it looks like you should be able to read the x and y values from the Rengtable object directly instead of using xPos and yPos.

The Rectangle object gives you a pretty easy way to test for collisions. At one point you will get performance problems from it (you're forced to check every game object against every other object) but for games with very few colliding entities this approach works fine, using it until it gives you problem is probably a good idea (code the game instead of worrying of problems that doesn't exists yet).

tl;dr: If you get bugs like this then there is a deeper problem with the code. Fix it and I'm happy :p
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top