Hello,
I have a program which creates multiple balls and bounces them off when they collide. This works fine.

import java.awt.Rectangle;
public class Ball{
    private int x = 0;        
    private int y = 0;        
    private int radius;
    private int panelwidth = 500;
    private int panelheight = 500;
    private int xDx = 1;        
    private int yDy = 1;        
    private boolean xUp, yUp;
    public Ball(int x, int y, int radius){
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.xUp = false;
        this.yUp = false;
        this.xDx = 1;
        this.yDy = 1;
    }

    public void move(){
        if ( y <= 0 ) {
            yUp = true;
            yDy = ( int ) ( Math.random() * 5 + 2 );
        }
        else if ( y >= this.panelheight - 2* this.radius ) {
            yDy = ( int ) ( Math.random() * 5 + 2 );
            yUp = false;
        }
        if ( x <= 0 ) {
            xUp = true;
            xDx = ( int ) ( Math.random() * 5 + 2 );
        }
        else if ( x >= this.panelwidth - 2 * this.radius ) {
            xUp = false;
            xDx = ( int ) ( Math.random() * 5 + 2 );
        }
        if (xUp)
            x += xDx;
        else
            x -= xDx;
        if ( yUp )
            y += yDy;
        else
            y -= yDy;
    }

    public int getX(){
        return this.x;
    }

    public int getY(){
        return this.y;
    }

    public void changeDirection(Ball b){
        if (this.x <= b.x && this.y <= b.y){
            this.xUp = false;
            this.yUp = false;
            b.xUp = true;
            b.yUp = true;
        }
        else if (this.x <= b.x && this.y >= b.y){
            this.xUp = false;
            this.yUp = true;
            b.xUp = true;
            b.yUp = false;
        }
    }

    public void setY(int y ){
        this.y = y;
    }

    public int getRadius(){
        return this.radius;
    }

    public Rectangle getBounds(){
        return new Rectangle(this.x, this.y, this.radius, this.radius);
    }

    public boolean collides(Ball b){
        return this.getBounds().intersects(b.getBounds());
    }

}



import javax.swing.JFrame;

public class Frame{
    public static void main( String args[] ) {
        JFrame frame = new JFrame( "Bouncing Balls" );
        Ball b[] = new Ball[6];
        b[0] = new Ball (10, 10,40);
        b[1] = new Ball (100, 100, 40);
        b[2] = new Ball (20, 188,40);
        b[3] = new Ball (144, 100,40);
        b[4] = new Ball (220, 138,40);
        b[5] = new Ball (14, 10,40);

        BallPanel bp = new BallPanel(b); 
        frame.add( bp );
        frame.setSize( 500, 500 ); 
        frame.setVisible( true ); 
    }
}





import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Color;
public class BallPanel extends JPanel implements ActionListener {
    private int delay = 10;
    protected Timer timer;
    Ball b[];
    public BallPanel(Ball b[]) {
        this.b= new Ball[b.length];
        for (int i = 0; i <b.length; i++){
            this.b[i] = b[i];
        }
        timer = new Timer(delay, this);
        timer.start();        
    }

    public void actionPerformed(ActionEvent e) {
        repaint();
    }

    public void paintComponent( Graphics g )  {
        super.paintComponent( g ); 
        g.setColor(Color.red);
        // move the balls
        for (int i = 0; i <b.length; i++){
            this.b[i].move();
        }
        // check for collision and change direction if balls collide
         for (int i = 0; i <b.length; i++){
              for (int j = 0; j <b.length; j++){
                  if (i!= j && b[i].collides(b[j])){
                      b[i].changeDirection(b[j]);
                  }
              }

        }
        // draw the balls
        for (int i = 0; i <b.length; i++){
            g.fillOval(this.b[i].getX(),  this.b[i].getY() , this.b[i].getRadius(), this.b[i].getRadius());
        }
    }
}

I would like to add a new ball when two balls collide in the Ball Panel class. When I tried creating a new ball in the nested loop that checks for collision (used an array list), and then called the move and the fill oval methods, the program froze. Can you tell me why? What is the proper way to handle creating a new ball upon collision and make it move etc.?

Thank you

aishamushtaq commented: Its really helpful. +0

Some minor syntax fixes. Some things to consider are the collision and distance of drawing the next ball. A ball inside a ball's radius will cause numerous collisions. Even the starting ball locations can be already causing collisions. I added a collision counter display. Increased the maximum balls to make to 50. The timer delay can also impact the collision detection. Try this and see if you still get freezing.

Ball.java:

import java.awt.Rectangle;
public class Ball{
    private int x = 0;
    private int y = 0;
    private int radius;
    private int panelwidth = 500;
    private int panelheight = 500;
    private int xDx = 1;
    private int yDy = 1;
    private boolean xUp, yUp;
    public Ball(int x, int y, int radius){
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.xUp = false;
        this.yUp = false;
        this.xDx = 1;
        this.yDy = 1;
    }

    public void move(){
        if ( y <= 0 ) {
            yUp = true;
            yDy = ( int ) ( Math.random() * 5 + 2 );
        }
        else if ( y >= this.panelheight - 2* this.radius ) {
            yDy = ( int ) ( Math.random() * 5 + 2 );
            yUp = false;
        }
        if ( x <= 0 ) {
            xUp = true;
            xDx = ( int ) ( Math.random() * 5 + 2 );
        }
        else if ( x >= this.panelwidth - 2 * this.radius ) {
            xUp = false;
            xDx = ( int ) ( Math.random() * 5 + 2 );
        }
        if (xUp)
            x += xDx;
        else
            x -= xDx;
        if ( yUp )
            y += yDy;
        else
            y -= yDy;
    }

    public int getX(){
        return this.x;
    }

    public int getY(){
        return this.y;
    }

    public boolean changeDirection(Ball b){
        if (this.x <= b.x && this.y <= b.y){
            this.xUp = false;
            this.yUp = false;
            b.xUp = true;
            b.yUp = true;
            return true;
        }
        else if (this.x <= b.x && this.y >= b.y){
            this.xUp = false;
            this.yUp = true;
            b.xUp = true;
            b.yUp = false;
            return true;
        }
        return false;
    }


    public void setY(int y ){
        this.y = y;
    }

    public int getRadius(){
        return this.radius;
    }

    public Rectangle getBounds(){
        return new Rectangle(this.x, this.y, this.radius, this.radius);
    }

    public boolean collides(Ball b){
        return this.getBounds().intersects(b.getBounds());
    }

    public boolean containsPoint(int x, int y) {
        int dx = this.x - x;
        int dy = this.y - y;
        int distanceSquared = dx * dx + dy * dy;
        return distanceSquared <= radius * radius;
    }
}

BallPanel.java:

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;

public class BallPanel extends JPanel implements ActionListener {
    private static final int delay = 16;
    protected Timer timer;
    ArrayList<Ball> b = new ArrayList<>();
    public static int collisionCount = 0;
    private static final int MAX_BALLS = 50;
    public BallPanel(Ball[] balls) {
        b.addAll(Arrays.asList(balls));
        timer = new Timer(delay, this);
        timer.start();
    }

    public void actionPerformed(ActionEvent e) {
        repaint();
    }

    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.red);
        // move the balls
        for (int i = 0; i < b.size(); i++) {
            this.b.get(i).move();
        }
        // check for collision and change direction if balls collide
        for (int i = 0; i < b.size(); i++) {
            for (int j = i + 1; j < b.size(); j++) {
                if (i != j && b.get(i).collides(b.get(j))) {
                    if (b.get(i).changeDirection(b.get(j))) {
                        collisionCount++; // Increment collisionCount here
                        // Add a new ball when a collision occurs and the number of balls is less than MAX_BALLS
                        if (b.size() < MAX_BALLS) {
                            int collisionX = b.get(i).getX();
                            int collisionY = b.get(i).getY();
                            b.add(new Ball(collisionX, collisionY, 40)); // You can customize the new ball's parameters
                        }
                    }
                }
            }
        }
        // draw the balls
        for (int i = 0; i < b.size(); i++) {
            g.fillOval(this.b.get(i).getX(), this.b.get(i).getY(), this.b.get(i).getRadius(), this.b.get(i).getRadius());
        }

        // display the collisionCount
        g.setColor(Color.black);
        g.drawString("Collision Count: " + collisionCount, 20, 20);
    }
}

Frame.java:

import javax.swing.JFrame;

public class Frame{
    public static void main(String[] args) {
        JFrame frame = new JFrame( "Bouncing Balls" );
        Ball[] b = new Ball[2]; // Start with 2 balls
       b[0] = new Ball (100, 100,40);

        for (int i = 1; i < b.length; i++) {
            int x, y;
            do {
                x = 10 + i * 10;
                y = 10 + i * 10;
            } while (!isFarEnough(x, y, b, i));
            b[i] = new Ball (x, y, 40);
        }
        BallPanel bp = new BallPanel(b);
        frame.add( bp );
        frame.setSize( 500, 500 );
        frame.setVisible( true );
    }

    private static boolean isFarEnough(int x, int y, Ball[] balls, int upToIndex) {
        for (int i = 0; i < upToIndex; i++) {
            Ball ball = balls[i];
            int dx = ball.getX() - x;
            int dy = ball.getY() - y;
            int distanceSquared = dx * dx + dy * dy;
            if (distanceSquared <= ball.getRadius() * ball.getRadius()) {
                return false;
            }
        }
        return true;
    }
}

Thank you so much for your reply and additions! It is certainly working as I wanted. I will read through the code you have added and ask you any questions I might have. I hope you don't mind.

Regards

… oh, sorry, missed the bit about you still needing clarifications. Feel feee to ask :) I’ll now unmark this topic as solved. /facepalm

I cleaned up the code some more. Removed some things that were not being used. I ran into a couple problems which caused the freeze you mentioned. It was memory heap space and array allocating. E.g., if x or y becomes negative, and also infinite recursion. I added a x/y coordinate display for a ball, and tinkered with the placement of the incremental balls being added, so it wouldn't add a lot at each collision. Hope that helps.

Ball.java

import java.awt.Rectangle;
public class Ball{
    private int x;
    private int y;
    private final int radius;
    final int panelheight = 500;
    final int panelwidth = 500;
    private int xDx = 1;
    private int yDy = 1;
    private boolean xUp, yUp = false;
    public Ball(int x, int y, int radius){
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.xUp = false;

    }

    public void move(){
        if ( y <= 0 ) {
            yUp = true;
            yDy = ( int ) ( Math.random() * 5 + 2 );
        }
        else if ( y >= this.panelheight - 2* this.radius ) {
            yDy = ( int ) ( Math.random() * 5 + 2 );
            yUp = false;
        }
        if ( x <= 0 ) {
            xUp = true;
            xDx = ( int ) ( Math.random() * 5 + 2 );
        }
        else if ( x >= this.panelwidth - 2 * this.radius ) {
            xUp = false;
            xDx = ( int ) ( Math.random() * 5 + 2 );
        }
        if (xUp)
            x += xDx;
        else
            x -= xDx;
        if ( yUp )
            y += yDy;
        else
            y -= yDy;
    }

    public int getX(){
        return this.x;
    }

    public int getY(){
        return this.y;
    }


    public boolean changeDirection(Ball b){
        if (this.x <= b.x && this.y <= b.y){
            this.xUp = false;
            this.yUp = false;
            b.xUp = true;
            b.yUp = true;
            return true;
        }
        else if (this.x <= b.x){
            this.xUp = false;
            this.yUp = true;
            b.xUp = true;
            b.yUp = false;
            return true;
        }
        return false;
    }

    public int getRadius(){
        return this.radius;
    }

    public Rectangle getBounds(){
        return new Rectangle(this.x, this.y, this.radius, this.radius);
    }

    public boolean collides(Ball b){
        return this.getBounds().intersects(b.getBounds());
    }
}

BallPanel.java:

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Graphics;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;

public class BallPanel extends JPanel implements ActionListener {
    private static final int delay = 1;
    protected Timer timer;
    ArrayList<Ball> b = new ArrayList<>();
    public static long collisionCount = 0;
    private static final int MAX_BALLS = 50;

    public BallPanel(Ball[] balls) {
        b.addAll(Arrays.asList(balls));
        timer = new Timer(delay, this);
        timer.start();
    }

    public void actionPerformed(ActionEvent e) {
        repaint();
    }

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

        // Move the balls
        for (int i = 0; i < b.size(); i++) {
            this.b.get(i).move();
        }

        // Check for collision and change direction if balls collide
        for (int i = 0; i < b.size(); i++) {
            for (int j = i + 1; j < b.size(); j++) {
                if (i != j && b.get(i).collides(b.get(j))) {
                    if (b.get(i).changeDirection(b.get(j))) {
                        collisionCount++; // Increment collisionCount here
                        int collisionX = b.get(i).getX();
                        int collisionY = b.get(i).getY();
                        // Add a new ball when a collision occurs and the number of balls is less than MAX_BALLS
                        if (b.size() < MAX_BALLS) {
                            // Calculate a new position for the ball that avoids collisions
                            collisionX = b.get(i).getX() - 50; // Example: Move 80 pixels to the right
                            collisionY = b.get(i).getY() - 25; // Example: Move 80 pixels up

                            b.add(new Ball(collisionX, collisionY, 25)); // You can customize the new ball's parameters
                        }
                    }
                }
            }
        }

// Draw the balls
        for (Ball ball : b) {
            // Draw the ball using its position and size
             g.setColor(Color.red);
            g.fillOval(ball.getX(), ball.getY(), 2 * ball.getRadius(), 2 * ball.getRadius());
        }

        // Display the collisionCount
        g.setColor(Color.black);
        g.drawString("Collision Count: " + collisionCount, 20, 20);
        g.drawString("X: " + b.get(1).getX(), 180, 20);
        g.drawString("Y: " + b.get(1).getY(), 220, 20);
    }
}

Frame.java:

import javax.swing.JFrame;

public class Frame {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Bouncing Balls");
        Ball[] b; // Start with 2 balls
        b = new Ball[2];
        b[0] = new Ball(150, 150, 25);

        for (int i = 1; i < b.length; i++) {
            int x, y;
            do {
                x = 10 + i * 10;
                y = 10 + i * 10;
            } while (!isFarEnough(x, y, b, i));
            b[i] = new Ball(x, y, 25);
        }
        BallPanel bp = new BallPanel(b);
        frame.add(bp);
        frame.setSize(520, 540); // Set the frame size to 520x540
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Close the application when the frame is closed
        frame.setVisible(true);
    }

    private static boolean isFarEnough(int x, int y, Ball[] balls, int upToIndex) {
        for (int i = 0; i < upToIndex; i++) {
            Ball ball = balls[i];
            int dx = ball.getX() - x;
            int dy = ball.getY() - y;
            int distanceSquared = dx * dx + dy * dy;
            if (distanceSquared <= ball.getRadius() * ball.getRadius()) {
                return false;
            }
        }
        return true;
    }
}

Hello,
Thank you again so much! All your additions are clear, but it seems like whenever two balls collide, more than one ball is added to the arraylist and painted. Any way to have just one ball added to the panel for every collision? Have some kind of a toggle flag in paintcomponent?

Regards

Yes, this is true. Overlapping ball spawns can do that causing a chain reaction. Something like that could be done. How you handle the spawning of the new ball is what I played with a bit before.

You will see better collision detection changing to:

 g.fillOval(ball.getX(), ball.getY(), 1 * ball.getRadius(), 1 * ball.getRadius());

Got it. Thank you so much!

Hi, Its helpful for me. I got my answer.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.