Search code examples
javaswingcollision-detectioncollisiongame-physics

Physics with bouncing balls


Is this the right site to ask this question, since I've been referred to another site two times by now.

I'm trying to create bouncing balls with realistic physics. At the moment, when the balls hit each other, they bounce back in the same direction they were coming from, now, I've searched the internet for how to do this, but I only find things about how to detect collision, not what to do after. I don't know a lot about physics, are their topics I should learn first before I would be able to do this? Here's an image of how I imagine balls would bounce in real life. Is this how it works?

how I think it should work
(source: thewombatguru.nl)

Also, do I have bad practises in my code? (probably, but I'm learning a lot and I like it)

This is my current code:

Asteroids.java

package Asteroids;

import javax.swing.*;

public class Asteroids {

    public static void createAndShowGui() {
        GamePanel gamePanel = new GamePanel();
        JFrame frame = new JFrame("Asteroids");
        frame.getContentPane().add(gamePanel);
        frame.pack();
        frame.setVisible(true);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocation(2000, 50);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

}

GamePanel.java

package Asteroids;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

public class GamePanel extends JPanel implements ActionListener {

    private final int WIDTH = 1000;
    private final int HEIGHT = 1000;

    Timer animationTimer;

    ArrayList<Rock> rocks;

    public GamePanel() {
        Dimension preferredDimension = new Dimension(WIDTH, HEIGHT);
        setPreferredSize(preferredDimension);

        animationTimer = new Timer(10, this);

        setUp();
    }

    public void setUp() {

        rocks = new ArrayList<>();

        rocks.add(new Rock(475, 1000, 0, -1));
        rocks.add(new Rock(0, 500, 1, 0));
        //rocks.add(new Rock(300, 270, -2, 2));
        //rocks.add(new Rock(400, 315, -5, -1));

        animationTimer.start();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        repaint();

        for (Rock rock : rocks) {
            for (Rock rockToCheck : rocks) {
                if (!rock.equals(rockToCheck)) {
                    rock.checkForCollisionWithRocks(rockToCheck);
                }
            }
            rock.checkForCollisionWithFrame(WIDTH, HEIGHT);
            rock.setxPos(rock.getxPos() + rock.getxVelocity());
            rock.setyPos(rock.getyPos() + rock.getyVelocity());
        }
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g.create();

        RenderingHints mainRenderingHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHints(mainRenderingHints);

        for (Rock rock : rocks) {
            rock.display(g2d);
        }

        g2d.dispose();
    }
}

Rock.java

package Asteroids;

import java.awt.*;

public class Rock {

    private int xPos;
    private int yPos;
    private int rockWidth;
    private int rockHeight;
    private int xVelocity;
    private int yVelocity;
    private int rockRadius;
    private Color rockColor;

    public Rock(int xPos, int yPos, int xVelocity, int yVelocity) {
        this.xPos = xPos;
        this.yPos = yPos;
        this.xVelocity = xVelocity;
        this.yVelocity = yVelocity;
        rockWidth = 50;
        rockHeight = rockWidth;
        rockRadius = rockWidth / 2;
        rockColor = new Color((int) (Math.random() * 255),(int) (Math.random() * 255),(int) (Math.random() * 255));
    }

    public void setxPos(int xPos) {
        this.xPos = xPos;
    }
    public int getxPos() {
        return xPos;
    }
    public int getRockWidth() {
        return rockWidth;
    }
    public void setRockWidth(int rockWidth) {
        this.rockWidth = rockWidth;
    }
    public int getRockHeight() {
        return rockHeight;
    }
    public void setRockHeight(int rockHeight) {
        this.rockHeight = rockHeight;
    }
    public int getyPos() {
        return yPos;
    }
    public void setyPos(int yPos) {
        this.yPos = yPos;
    }
    public int getxVelocity() {
        return xVelocity;
    }
    public void setxVelocity(int xVelocity) {
        this.xVelocity = xVelocity;
    }
    public int getyVelocity() {
        return yVelocity;
    }
    public void setyVelocity(int yVelocity) {
        this.yVelocity = yVelocity;
    }
    public int getRockRadius() {
        return rockRadius;
    }
    public void setRockRadius(int rockRadius) {
        this.rockRadius = rockRadius;
    }

    public void checkForCollisionWithRocks(Rock rock) {
        int radiusOfBoth = rock.getRockRadius() + rockRadius;
        int horDistance = Math.abs((rock.getxPos() + rock.getRockRadius()) - (xPos + rockRadius));
        int verDistance = Math.abs((rock.getyPos() + rock.getRockRadius()) - (yPos + rockRadius));
        int diagDistance = (int) Math.sqrt(Math.pow(horDistance, 2) + Math.pow(verDistance, 2));

        if (diagDistance <= radiusOfBoth) {

            xVelocity = -xVelocity;
            yVelocity = -yVelocity;

            rock.setxVelocity(-rock.getxVelocity());
            rock.setyVelocity(-rock.getyVelocity());
            rock.setxPos(rock.getxPos() + rock.getxVelocity());
            rock.setyPos(rock.getyPos() + rock.getyVelocity());
        }
    }

    public void checkForCollisionWithFrame(final int WIDTH, final int HEIGHT) {
        if (xPos < 0) {
            xVelocity *= -1;
            xPos = 0;
        } else if (xPos + rockWidth > WIDTH) {
            xVelocity *= -1;
            xPos = WIDTH - rockWidth;
        }

        if (yPos < 0) {
            yVelocity *= -1;
            yPos = 0;
        } else if (yPos + rockHeight > HEIGHT) {
            yVelocity *= -1;
            yPos = HEIGHT - rockHeight;
        }
    }

    public void display(Graphics2D g2d) {
        g2d.setColor(rockColor);
        g2d.fillOval(xPos, yPos, rockWidth, rockHeight);
    }

}

Can anyone help ?


Solution

  • Real life physics is tricky (gravity, inertia, etc), but to start with, bouncing the balls off of each other:

    When the two balls hit, there's an angle of collision. Luckily, because they are circles (assuming) you can find the angle of incidence by finding the angle of the line going through the center of the two balls. Then you want to send 1 ball off 1 way perpendicular the that line, and the other the other way.

    Make sense?