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
447
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
447
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.
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1
  • The Helper The Helper:
    New recipe is another summer dessert Berry and Peach Cheesecake - https://www.thehelper.net/threads/recipe-berry-and-peach-cheesecake.194169/

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top